Merge branch 'release/0.6.0'

This commit is contained in:
2022-11-03 11:56:25 -05:00
138 changed files with 12765 additions and 563 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@ target/
*.iml
.env
.idea
#############################################
## Original contents from github template: ##

View File

@ -33,6 +33,7 @@
<module>qqq-backend-module-api</module>
<module>qqq-backend-module-filesystem</module>
<module>qqq-backend-module-rdbms</module>
<module>qqq-language-support-javascript</module>
<module>qqq-middleware-picocli</module>
<module>qqq-middleware-javalin</module>
<module>qqq-middleware-lambda</module>
@ -41,7 +42,7 @@
</modules>
<properties>
<revision>0.6.0-SNAPSHOT</revision>
<revision>0.6.0</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.actions.dashboard;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
@ -51,6 +52,8 @@ public class QuickSightChartRenderer extends AbstractWidgetRenderer
@Override
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
{
ActionHelper.validateSession(input);
try
{
QuickSightChartMetaData quickSightMetaData = (QuickSightChartMetaData) input.getWidgetMetaData();

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.actions.dashboard;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
@ -40,6 +41,8 @@ public class RenderWidgetAction
*******************************************************************************/
public RenderWidgetOutput execute(RenderWidgetInput input) throws QException
{
ActionHelper.validateSession(input);
AbstractWidgetRenderer widgetRenderer = QCodeLoader.getAdHoc(AbstractWidgetRenderer.class, input.getWidgetMetaData().getCodeReference());
return (widgetRenderer.render(input));
}

View File

@ -0,0 +1,40 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.interfaces;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
/*******************************************************************************
** Interface for the Get action.
**
*******************************************************************************/
public interface GetInterface
{
/*******************************************************************************
**
*******************************************************************************/
GetOutput execute(GetInput getInput) throws QException;
}

View File

@ -30,6 +30,7 @@ import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
@ -69,8 +70,10 @@ public class MetaDataAction
Map<String, QFrontendTableMetaData> tables = new LinkedHashMap<>();
for(Map.Entry<String, QTableMetaData> entry : metaDataInput.getInstance().getTables().entrySet())
{
tables.put(entry.getKey(), new QFrontendTableMetaData(entry.getValue(), false));
treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue()));
String tableName = entry.getKey();
QBackendMetaData backendForTable = metaDataInput.getInstance().getBackendForTable(tableName);
tables.put(tableName, new QFrontendTableMetaData(backendForTable, entry.getValue(), false));
treeNodes.put(tableName, new AppTreeNode(entry.getValue()));
}
metaDataOutput.setTables(tables);

View File

@ -27,6 +27,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
@ -52,7 +53,8 @@ public class TableMetaDataAction
{
throw (new QNotFoundException("Table [" + tableMetaDataInput.getTableName() + "] was not found."));
}
tableMetaDataOutput.setTable(new QFrontendTableMetaData(table, true));
QBackendMetaData backendForTable = tableMetaDataInput.getInstance().getBackendForTable(table.getName());
tableMetaDataOutput.setTable(new QFrontendTableMetaData(backendForTable, table, true));
// todo post-customization - can do whatever w/ the result if you want

View File

@ -23,26 +23,43 @@ package com.kingsrook.qqq.backend.core.actions.processes;
import java.io.Serializable;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
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.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
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.processes.implementations.basepull.BasepullConfiguration;
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.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -55,6 +72,15 @@ public class RunProcessAction
{
private static final Logger LOG = LogManager.getLogger(RunProcessAction.class);
public static final String BASEPULL_THIS_RUNTIME_KEY = "basepullThisRuntimeKey";
public static final String BASEPULL_LAST_RUNTIME_KEY = "basepullLastRuntimeKey";
public static final String BASEPULL_TIMESTAMP_FIELD = "basepullTimestampField";
////////////////////////////////////////////////////////////////////////////////////////////////
// indicator that the timestamp field should be updated - e.g., the execute step is finished. //
////////////////////////////////////////////////////////////////////////////////////////////////
public static final String BASEPULL_READY_TO_UPDATE_TIMESTAMP_FIELD = "basepullReadyToUpdateTimestamp";
/*******************************************************************************
@ -84,6 +110,18 @@ public class RunProcessAction
UUIDAndTypeStateKey stateKey = new UUIDAndTypeStateKey(UUID.fromString(runProcessInput.getProcessUUID()), StateType.PROCESS_STATUS);
ProcessState processState = primeProcessState(runProcessInput, stateKey, process);
/////////////////////////////////////////////////////////
// if process is 'basepull' style, keep track of 'now' //
/////////////////////////////////////////////////////////
BasepullConfiguration basepullConfiguration = process.getBasepullConfiguration();
if(basepullConfiguration != null)
{
///////////////////////////////////////
// get the stored basepull timestamp //
///////////////////////////////////////
persistLastRunTime(runProcessInput, process, basepullConfiguration);
}
try
{
String lastStepName = runProcessInput.getStartAfterStep();
@ -151,6 +189,15 @@ public class RunProcessAction
throw (new QException("Unsure how to run a step of type: " + step.getClass().getName()));
}
}
////////////////////////////////////////////////////////////////////////////////////
// if 'basepull' style process, update the stored basepull timestamp //
// but only when we've been signaled to do so - i.e., after an Execute step runs. //
////////////////////////////////////////////////////////////////////////////////////
if(basepullConfiguration != null && BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(runProcessInput.getValue(BASEPULL_READY_TO_UPDATE_TIMESTAMP_FIELD))))
{
storeLastRunTime(runProcessInput, process, basepullConfiguration);
}
}
catch(QException qe)
{
@ -250,7 +297,17 @@ public class RunProcessAction
runBackendStepInput.setTableName(process.getTableName());
runBackendStepInput.setSession(runProcessInput.getSession());
runBackendStepInput.setCallback(runProcessInput.getCallback());
runBackendStepInput.setFrontendStepBehavior(runProcessInput.getFrontendStepBehavior());
runBackendStepInput.setAsyncJobCallback(runProcessInput.getAsyncJobCallback());
///////////////////////////////////////////////////////////////
// if 'basepull' values are in the inputs, add to step input //
///////////////////////////////////////////////////////////////
if(runProcessInput.getValues().containsKey(BASEPULL_LAST_RUNTIME_KEY))
{
runBackendStepInput.setBasepullLastRunTime((Instant) runProcessInput.getValues().get(BASEPULL_LAST_RUNTIME_KEY));
}
RunBackendStepOutput lastFunctionResult = new RunBackendStepAction().execute(runBackendStepInput);
storeState(stateKey, lastFunctionResult.getProcessState());
@ -368,4 +425,129 @@ public class RunProcessAction
return (getStateProvider().get(ProcessState.class, stateKey));
}
/*******************************************************************************
** Insert or update the last runtime value for this basepull into the backend.
*******************************************************************************/
protected void storeLastRunTime(RunProcessInput runProcessInput, QProcessMetaData process, BasepullConfiguration basepullConfiguration) throws QException
{
String basepullTableName = basepullConfiguration.getTableName();
String basepullKeyFieldName = basepullConfiguration.getKeyField();
String basepullLastRunTimeFieldName = basepullConfiguration.getLastRunTimeFieldName();
String basepullKeyValue = (basepullConfiguration.getKeyValue() != null) ? basepullConfiguration.getKeyValue() : process.getName();
///////////////////////////////////////
// get the stored basepull timestamp //
///////////////////////////////////////
QueryInput queryInput = new QueryInput(runProcessInput.getInstance());
queryInput.setSession(runProcessInput.getSession());
queryInput.setTableName(basepullTableName);
queryInput.setFilter(new QQueryFilter().withCriteria(
new QFilterCriteria()
.withFieldName(basepullKeyFieldName)
.withOperator(QCriteriaOperator.EQUALS)
.withValues(List.of(basepullKeyValue))));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
//////////////////////////////////////////
// get the runtime for this process run //
//////////////////////////////////////////
Instant newRunTime = (Instant) runProcessInput.getValues().get(BASEPULL_THIS_RUNTIME_KEY);
/////////////////////////////////////////////////
// update if found, otherwise insert new value //
/////////////////////////////////////////////////
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
{
///////////////////////////////////////////////////////////////////////////////
// update the basepull table with 'now' (which is before original query ran) //
///////////////////////////////////////////////////////////////////////////////
QRecord basepullRecord = queryOutput.getRecords().get(0);
basepullRecord.setValue(basepullLastRunTimeFieldName, newRunTime);
////////////
// update //
////////////
UpdateInput updateInput = new UpdateInput(runProcessInput.getInstance());
updateInput.setSession(runProcessInput.getSession());
updateInput.setTableName(basepullTableName);
updateInput.setRecords(List.of(basepullRecord));
new UpdateAction().execute(updateInput);
}
else
{
QRecord basepullRecord = new QRecord()
.withValue(basepullKeyFieldName, basepullKeyValue)
.withValue(basepullLastRunTimeFieldName, newRunTime);
////////////////////////////////
// insert new basepull record //
////////////////////////////////
InsertInput insertInput = new InsertInput(runProcessInput.getInstance());
insertInput.setSession(runProcessInput.getSession());
insertInput.setTableName(basepullTableName);
insertInput.setRecords(List.of(basepullRecord));
new InsertAction().execute(insertInput);
}
}
/*******************************************************************************
** Lookup the last runtime for this basepull, and set it (plus now) in the process's
** values.
*******************************************************************************/
protected void persistLastRunTime(RunProcessInput runProcessInput, QProcessMetaData process, BasepullConfiguration basepullConfiguration) throws QException
{
////////////////////////////////////////////////////////
// if these values were already computed, don't re-do //
////////////////////////////////////////////////////////
if(runProcessInput.getValue(BASEPULL_THIS_RUNTIME_KEY) != null)
{
return;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// store 'now', which will be used to update basepull record if process completes successfully //
/////////////////////////////////////////////////////////////////////////////////////////////////
Instant now = Instant.now();
runProcessInput.getValues().put(BASEPULL_THIS_RUNTIME_KEY, now);
String basepullTableName = basepullConfiguration.getTableName();
String basepullKeyFieldName = basepullConfiguration.getKeyField();
String basepullLastRunTimeFieldName = basepullConfiguration.getLastRunTimeFieldName();
Integer basepullHoursBackForInitialTimestamp = basepullConfiguration.getHoursBackForInitialTimestamp();
String basepullKeyValue = (basepullConfiguration.getKeyValue() != null) ? basepullConfiguration.getKeyValue() : process.getName();
///////////////////////////////////////
// get the stored basepull timestamp //
///////////////////////////////////////
QueryInput queryInput = new QueryInput(runProcessInput.getInstance());
queryInput.setSession(runProcessInput.getSession());
queryInput.setTableName(basepullTableName);
queryInput.setFilter(new QQueryFilter().withCriteria(
new QFilterCriteria()
.withFieldName(basepullKeyFieldName)
.withOperator(QCriteriaOperator.EQUALS)
.withValues(List.of(basepullKeyValue))));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
///////////////////////////////////////////////////////////////////////////////////////////////////
// get the stored time, if not, default to 'now' unless a number of hours to offset was provided //
///////////////////////////////////////////////////////////////////////////////////////////////////
Instant lastRunTime = now;
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
{
QRecord basepullRecord = queryOutput.getRecords().get(0);
lastRunTime = ValueUtils.getValueAsInstant(basepullRecord.getValue(basepullLastRunTimeFieldName));
}
else if(basepullHoursBackForInitialTimestamp != null)
{
lastRunTime = lastRunTime.minus(basepullHoursBackForInitialTimestamp, ChronoUnit.HOURS);
}
runProcessInput.getValues().put(BASEPULL_LAST_RUNTIME_KEY, lastRunTime);
runProcessInput.getValues().put(BASEPULL_TIMESTAMP_FIELD, basepullConfiguration.getTimestampField());
}
}

View File

@ -0,0 +1,119 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.Log4jCodeExecutionLogger;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.exceptions.QCodeException;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
/*******************************************************************************
** Action to execute user/runtime defined code.
**
** This action is designed to support code in multiple languages, by using
** executors, e.g., provided by additional runtime qqq dependencies. Initially
** we are building qqq-language-support-javascript.
**
** We also have a Java executor, to provide at least a little bit of testability
** within qqq-backend-core. This executor is a candidate to be replaced in the
** future with something that would do actual dynamic java (whether that's compiled
** at runtime, or loaded from a plugin jar at runtime). In other words, the java
** executor in place today is just meant to be a placeholder.
*******************************************************************************/
public class ExecuteCodeAction
{
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("checkstyle:indentation")
public void run(ExecuteCodeInput input, ExecuteCodeOutput output) throws QException, QCodeException
{
QCodeReference codeReference = input.getCodeReference();
QCodeExecutionLoggerInterface executionLogger = input.getExecutionLogger();
if(executionLogger == null)
{
executionLogger = getDefaultExecutionLogger();
}
executionLogger.acceptExecutionStart(input);
try
{
String languageExecutor = switch(codeReference.getCodeType())
{
case JAVA -> "com.kingsrook.qqq.backend.core.actions.scripts.QJavaExecutor";
case JAVA_SCRIPT -> "com.kingsrook.qqq.languages.javascript.QJavaScriptExecutor";
};
@SuppressWarnings("unchecked")
Class<? extends QCodeExecutor> executorClass = (Class<? extends QCodeExecutor>) Class.forName(languageExecutor);
QCodeExecutor qCodeExecutor = executorClass.getConstructor().newInstance();
////////////////////////////////////////////////////////////////////////////////////////////////////
// merge all of the input context, plus the input... input - into a context for the code executor //
////////////////////////////////////////////////////////////////////////////////////////////////////
Map<String, Serializable> context = new HashMap<>();
if(input.getContext() != null)
{
context.putAll(input.getContext());
}
if(input.getInput() != null)
{
context.putAll(input.getInput());
}
Serializable codeOutput = qCodeExecutor.execute(codeReference, context, executionLogger);
output.setOutput(codeOutput);
executionLogger.acceptExecutionEnd(codeOutput);
}
catch(QCodeException qCodeException)
{
executionLogger.acceptException(qCodeException);
throw (qCodeException);
}
catch(Exception e)
{
executionLogger.acceptException(e);
throw (new QException("Error executing code [" + codeReference + "]", e));
}
}
/*******************************************************************************
**
*******************************************************************************/
private QCodeExecutionLoggerInterface getDefaultExecutionLogger()
{
return (new Log4jCodeExecutionLogger());
}
}

View File

@ -0,0 +1,44 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.exceptions.QCodeException;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
/*******************************************************************************
** Interface to be implemented by language-specific code executors, e.g., in
** qqq-language-support-${languageName} maven modules.
*******************************************************************************/
public interface QCodeExecutor
{
/*******************************************************************************
**
*******************************************************************************/
Serializable execute(QCodeReference codeReference, Map<String, Serializable> inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException;
}

View File

@ -0,0 +1,68 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.exceptions.QCodeException;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
/*******************************************************************************
** Java
*******************************************************************************/
public class QJavaExecutor implements QCodeExecutor
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public Serializable execute(QCodeReference codeReference, Map<String, Serializable> inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException
{
Map<String, Object> context = new HashMap<>(inputContext);
if(!context.containsKey("logger"))
{
context.put("logger", executionLogger);
}
Serializable output;
try
{
Function<Map<String, Object>, Serializable> function = QCodeLoader.getFunction(codeReference);
output = function.apply(context);
}
catch(Exception e)
{
QCodeException qCodeException = new QCodeException("Error executing script", e);
throw (qCodeException);
}
return (output);
}
}

View File

@ -0,0 +1,152 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
import java.util.HashMap;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptOutput;
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.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
import com.kingsrook.qqq.backend.core.model.scripts.Script;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
/*******************************************************************************
**
*******************************************************************************/
public class RunAssociatedScriptAction
{
/*******************************************************************************
**
*******************************************************************************/
public void run(RunAssociatedScriptInput input, RunAssociatedScriptOutput output) throws QException
{
ActionHelper.validateSession(input);
Serializable scriptId = getScriptId(input);
if(scriptId == null)
{
throw (new QNotFoundException("The input record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey()
+ "] does not have a script specified for [" + input.getCodeReference().getFieldName() + "]"));
}
Script script = getScript(input, scriptId);
if(script.getCurrentScriptRevisionId() == null)
{
throw (new QNotFoundException("The script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey()
+ "] (scriptId=" + scriptId + ") does not have a current version."));
}
ScriptRevision scriptRevision = getCurrentScriptRevision(input, script.getCurrentScriptRevisionId());
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(input.getInstance());
executeCodeInput.setSession(input.getSession());
executeCodeInput.setInput(new HashMap<>(input.getInputValues()));
executeCodeInput.setContext(new HashMap<>());
if(input.getOutputObject() != null)
{
executeCodeInput.getContext().put("output", input.getOutputObject());
}
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
executeCodeInput.setExecutionLogger(new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId()));
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
output.setOutput(executeCodeOutput.getOutput());
}
/*******************************************************************************
**
*******************************************************************************/
private ScriptRevision getCurrentScriptRevision(RunAssociatedScriptInput input, Serializable scriptRevisionId) throws QException
{
GetInput getInput = new GetInput(input.getInstance());
getInput.setSession(input.getSession());
getInput.setTableName("scriptRevision");
getInput.setPrimaryKey(scriptRevisionId);
GetOutput getOutput = new GetAction().execute(getInput);
if(getOutput.getRecord() == null)
{
throw (new QNotFoundException("The current revision of the script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() + "]["
+ input.getCodeReference().getFieldName() + "] (scriptRevisionId=" + scriptRevisionId + ") was not found."));
}
return (new ScriptRevision(getOutput.getRecord()));
}
/*******************************************************************************
**
*******************************************************************************/
private Script getScript(RunAssociatedScriptInput input, Serializable scriptId) throws QException
{
GetInput getInput = new GetInput(input.getInstance());
getInput.setSession(input.getSession());
getInput.setTableName("script");
getInput.setPrimaryKey(scriptId);
GetOutput getOutput = new GetAction().execute(getInput);
if(getOutput.getRecord() == null)
{
throw (new QNotFoundException("The script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() + "]["
+ input.getCodeReference().getFieldName() + "] (script id=" + scriptId + ") was not found."));
}
return (new Script(getOutput.getRecord()));
}
/*******************************************************************************
**
*******************************************************************************/
private Serializable getScriptId(RunAssociatedScriptInput input) throws QException
{
GetInput getInput = new GetInput(input.getInstance());
getInput.setSession(input.getSession());
getInput.setTableName(input.getCodeReference().getRecordTable());
getInput.setPrimaryKey(input.getCodeReference().getRecordPrimaryKey());
GetOutput getOutput = new GetAction().execute(getInput);
if(getOutput.getRecord() == null)
{
throw (new QNotFoundException("The requested record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() + "] was not found."));
}
return (getOutput.getRecord().getValue(input.getCodeReference().getFieldName()));
}
}

View File

@ -0,0 +1,222 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
import java.util.List;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.StoreAssociatedScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.StoreAssociatedScriptOutput;
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.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
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.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** Action to store a new version of a script, associated with a record.
**
** If there's never been a script assigned to the record (for the specified field),
** then a new Script record is first inserted.
**
** The script referenced by the record is always updated to point at the new
** scriptRevision record that is inserted.
**
*******************************************************************************/
public class StoreAssociatedScriptAction
{
/*******************************************************************************
**
*******************************************************************************/
public void run(StoreAssociatedScriptInput input, StoreAssociatedScriptOutput output) throws QException
{
ActionHelper.validateSession(input);
QTableMetaData table = input.getTable();
Optional<AssociatedScript> optAssociatedScript = table.getAssociatedScripts().stream().filter(as -> as.getFieldName().equals(input.getFieldName())).findFirst();
if(optAssociatedScript.isEmpty())
{
throw (new QException("Field to update associated script for is not an associated script field."));
}
AssociatedScript associatedScript = optAssociatedScript.get();
/////////////////////////////////////////////////////////////
// get the record that the script is to be associated with //
/////////////////////////////////////////////////////////////
QRecord associatedRecord;
{
GetInput getInput = new GetInput(input.getInstance());
getInput.setSession(input.getSession());
getInput.setTableName(input.getTableName());
getInput.setPrimaryKey(input.getRecordPrimaryKey());
getInput.setShouldGenerateDisplayValues(true);
GetOutput getOutput = new GetAction().execute(getInput);
associatedRecord = getOutput.getRecord();
}
if(associatedRecord == null)
{
throw (new QException("Record to associated with script was not found."));
}
//////////////////////////////////////////////////////////////////
// check if there's currently a script referenced by the record //
//////////////////////////////////////////////////////////////////
Serializable existingScriptId = associatedRecord.getValueString(input.getFieldName());
QRecord script;
Integer nextSequenceNo = 1;
if(existingScriptId == null)
{
////////////////////////////////////////////////////////////////////
// get the script type - that'll be part of the new script's name //
////////////////////////////////////////////////////////////////////
GetInput getInput = new GetInput(input.getInstance());
getInput.setSession(input.getSession());
getInput.setTableName("scriptType");
getInput.setPrimaryKey(associatedScript.getScriptTypeId());
getInput.setShouldGenerateDisplayValues(true);
GetOutput getOutput = new GetAction().execute(getInput);
QRecord scriptType = getOutput.getRecord();
if(scriptType == null)
{
throw (new QException("Script type [" + associatedScript.getScriptTypeId() + "] was not found."));
}
/////////////////////////
// insert a new script //
/////////////////////////
script = new QRecord();
script.setValue("scriptTypeId", associatedScript.getScriptTypeId());
script.setValue("name", associatedRecord.getRecordLabel() + " - " + scriptType.getRecordLabel());
InsertInput insertInput = new InsertInput(input.getInstance());
insertInput.setSession(input.getSession());
insertInput.setTableName("script");
insertInput.setRecords(List.of(script));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
script = insertOutput.getRecords().get(0);
/////////////////////////////////////////////////////////////
// update the associated record to point at the new script //
/////////////////////////////////////////////////////////////
UpdateInput updateInput = new UpdateInput(input.getInstance());
updateInput.setSession(input.getSession());
updateInput.setTableName(input.getTableName());
updateInput.setRecords(List.of(new QRecord()
.withValue(table.getPrimaryKeyField(), associatedRecord.getValue(table.getPrimaryKeyField()))
.withValue(input.getFieldName(), script.getValue("id"))
));
new UpdateAction().execute(updateInput);
}
else
{
////////////////////////////////////////
// get the existing script, to update //
////////////////////////////////////////
GetInput getInput = new GetInput(input.getInstance());
getInput.setSession(input.getSession());
getInput.setTableName("script");
getInput.setPrimaryKey(existingScriptId);
GetOutput getOutput = new GetAction().execute(getInput);
script = getOutput.getRecord();
QueryInput queryInput = new QueryInput(input.getInstance());
queryInput.setSession(input.getSession());
queryInput.setTableName("scriptRevision");
queryInput.setFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("scriptId", QCriteriaOperator.EQUALS, List.of(script.getValue("id"))))
.withOrderBy(new QFilterOrderBy("sequenceNo", false))
);
queryInput.setLimit(1);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
if(!queryOutput.getRecords().isEmpty())
{
nextSequenceNo = queryOutput.getRecords().get(0).getValueInteger("sequenceNo") + 1;
}
}
//////////////////////////////////
// insert a new script revision //
//////////////////////////////////
String commitMessage = input.getCommitMessage();
if(!StringUtils.hasContent(commitMessage))
{
if(nextSequenceNo == 1)
{
commitMessage = "Initial version";
}
else
{
commitMessage = "No commit message given";
}
}
QRecord scriptRevision = new QRecord()
.withValue("scriptId", script.getValue("id"))
.withValue("contents", input.getCode())
.withValue("commitMessage", commitMessage)
.withValue("sequenceNo", nextSequenceNo);
try
{
scriptRevision.setValue("author", input.getSession().getUser().getFullName());
}
catch(Exception e)
{
scriptRevision.setValue("author", "Unknown");
}
InsertInput insertInput = new InsertInput(input.getInstance());
insertInput.setSession(input.getSession());
insertInput.setTableName("scriptRevision");
insertInput.setRecords(List.of(scriptRevision));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
scriptRevision = insertOutput.getRecords().get(0);
////////////////////////////////////////////////////
// update the script to point at the new revision //
////////////////////////////////////////////////////
script.setValue("currentScriptRevisionId", scriptRevision.getValue("id"));
UpdateInput updateInput = new UpdateInput(input.getInstance());
updateInput.setSession(input.getSession());
updateInput.setTableName("script");
updateInput.setRecords(List.of(script));
new UpdateAction().execute(updateInput);
}
}

View File

@ -0,0 +1,65 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts;
import java.util.HashMap;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.BuildScriptLogAndScriptLogLineExecutionLogger;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput;
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.tables.QTableMetaData;
/*******************************************************************************
** Class for running a test of a script - e.g., maybe before it is saved.
*******************************************************************************/
public class TestScriptAction
{
/*******************************************************************************
**
*******************************************************************************/
public void run(TestScriptInput input, TestScriptOutput output) throws QException
{
QTableMetaData table = input.getTable();
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(input.getInstance());
executeCodeInput.setSession(input.getSession());
executeCodeInput.setInput(new HashMap<>(input.getInputValues()));
executeCodeInput.setContext(new HashMap<>());
// todo! if(input.getOutputObject() != null)
// todo! {
// todo! executeCodeInput.getContext().put("output", input.getOutputObject());
// todo! }
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(input.getCode()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
executeCodeInput.setExecutionLogger(new BuildScriptLogAndScriptLogLineExecutionLogger());
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
// todo! output.setOutput(executeCodeOutput.getOutput());
}
}

View File

@ -0,0 +1,221 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts.logging;
import java.io.Serializable;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Implementation of a code execution logger that builds a scriptLog and 0 or more
** scriptLogLine records - but doesn't insert them. e.g., useful for testing
** (both in junit, and for users in-app).
*******************************************************************************/
public class BuildScriptLogAndScriptLogLineExecutionLogger implements QCodeExecutionLoggerInterface
{
private static final Logger LOG = LogManager.getLogger(BuildScriptLogAndScriptLogLineExecutionLogger.class);
private QRecord scriptLog;
private List<QRecord> scriptLogLines = new ArrayList<>();
protected ExecuteCodeInput executeCodeInput;
private Serializable scriptId;
private Serializable scriptRevisionId;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public BuildScriptLogAndScriptLogLineExecutionLogger()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public BuildScriptLogAndScriptLogLineExecutionLogger(Serializable scriptId, Serializable scriptRevisionId)
{
this.scriptId = scriptId;
this.scriptRevisionId = scriptRevisionId;
}
/*******************************************************************************
**
*******************************************************************************/
private QRecord buildHeaderRecord(ExecuteCodeInput executeCodeInput)
{
return (new QRecord()
.withValue("scriptId", scriptId)
.withValue("scriptRevisionId", scriptRevisionId)
.withValue("startTimestamp", Instant.now())
.withValue("input", truncate(executeCodeInput.getInput())));
}
/*******************************************************************************
**
*******************************************************************************/
protected QRecord buildDetailLogRecord(String logLine)
{
return (new QRecord()
.withValue("scriptLogId", scriptLog.getValue("id"))
.withValue("timestamp", Instant.now())
.withValue("text", truncate(logLine)));
}
/*******************************************************************************
**
*******************************************************************************/
private String truncate(Object o)
{
return StringUtils.safeTruncate(ValueUtils.getValueAsString(o), 1000, "...");
}
/*******************************************************************************
**
*******************************************************************************/
protected void updateHeaderAtEnd(Serializable output, Exception exception)
{
Instant startTimestamp = (Instant) scriptLog.getValue("startTimestamp");
Instant endTimestamp = Instant.now();
scriptLog.setValue("endTimestamp", endTimestamp);
scriptLog.setValue("runTimeMillis", startTimestamp.until(endTimestamp, ChronoUnit.MILLIS));
if(exception != null)
{
scriptLog.setValue("hadError", true);
scriptLog.setValue("error", exception.getMessage());
}
else
{
scriptLog.setValue("hadError", false);
scriptLog.setValue("output", truncate(output));
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptExecutionStart(ExecuteCodeInput executeCodeInput)
{
try
{
this.executeCodeInput = executeCodeInput;
this.scriptLog = buildHeaderRecord(executeCodeInput);
}
catch(Exception e)
{
LOG.warn("Error starting storage of script log", e);
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptLogLine(String logLine)
{
scriptLogLines.add(buildDetailLogRecord(logLine));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptException(Exception exception)
{
updateHeaderAtEnd(null, exception);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptExecutionEnd(Serializable output)
{
updateHeaderAtEnd(output, null);
}
/*******************************************************************************
** Getter for scriptLog
**
*******************************************************************************/
public QRecord getScriptLog()
{
return scriptLog;
}
/*******************************************************************************
** Getter for scriptLogLines
**
*******************************************************************************/
public List<QRecord> getScriptLogLines()
{
return scriptLogLines;
}
/*******************************************************************************
** Setter for scriptLog
**
*******************************************************************************/
protected void setScriptLog(QRecord scriptLog)
{
this.scriptLog = scriptLog;
}
}

View File

@ -0,0 +1,93 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts.logging;
import java.io.Serializable;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Implementation of a code execution logger that logs to LOG 4j
*******************************************************************************/
public class Log4jCodeExecutionLogger implements QCodeExecutionLoggerInterface
{
private static final Logger LOG = LogManager.getLogger(Log4jCodeExecutionLogger.class);
private QCodeReference qCodeReference;
private String uuid = UUID.randomUUID().toString();
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptExecutionStart(ExecuteCodeInput executeCodeInput)
{
this.qCodeReference = executeCodeInput.getCodeReference();
String inputString = StringUtils.safeTruncate(ValueUtils.getValueAsString(executeCodeInput.getInput()), 250, "...");
LOG.info("Starting script execution: " + qCodeReference.getName() + ", uuid: " + uuid + ", with input: " + inputString);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptLogLine(String logLine)
{
LOG.info("Script log: " + uuid + ": " + logLine);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptException(Exception exception)
{
LOG.info("Script Exception: " + uuid, exception);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptExecutionEnd(Serializable output)
{
String outputString = StringUtils.safeTruncate(ValueUtils.getValueAsString(output), 250, "...");
LOG.info("Finished script execution: " + qCodeReference.getName() + ", uuid: " + uuid + ", with output: " + outputString);
}
}

View File

@ -0,0 +1,74 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts.logging;
import java.io.Serializable;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
/*******************************************************************************
** Implementation of a code execution logger that just noop's every action.
*******************************************************************************/
public class NoopCodeExecutionLogger implements QCodeExecutionLoggerInterface
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptExecutionStart(ExecuteCodeInput executeCodeInput)
{
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptLogLine(String logLine)
{
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptException(Exception exception)
{
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptExecutionEnd(Serializable output)
{
}
}

View File

@ -0,0 +1,64 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts.logging;
import java.io.Serializable;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
/*******************************************************************************
** Interface to provide logging functionality to QCodeExecution (e.g., scripts)
*******************************************************************************/
public interface QCodeExecutionLoggerInterface
{
/*******************************************************************************
** Called when the execution starts - takes the execution's input object.
*******************************************************************************/
void acceptExecutionStart(ExecuteCodeInput executeCodeInput);
/*******************************************************************************
** Called to log a line, a message.
*******************************************************************************/
void acceptLogLine(String logLine);
/*******************************************************************************
** In case the loggerInterface object is provided to the script as context,
** this method gives a clean interface for the script to log a line.
*******************************************************************************/
default void log(String message)
{
acceptLogLine(message);
}
/*******************************************************************************
** Called if the script fails with an exception.
*******************************************************************************/
void acceptException(Exception exception);
/*******************************************************************************
** Called if the script completes without exception.
*******************************************************************************/
void acceptExecutionEnd(Serializable output);
}

View File

@ -0,0 +1,136 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts.logging;
import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
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.utils.CollectionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Implementation of a code execution logger that logs into scriptLog and scriptLogLine
** tables - e.g., as defined in ScriptMetaDataProvider.
*******************************************************************************/
public class StoreScriptLogAndScriptLogLineExecutionLogger extends BuildScriptLogAndScriptLogLineExecutionLogger
{
private static final Logger LOG = LogManager.getLogger(StoreScriptLogAndScriptLogLineExecutionLogger.class);
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public StoreScriptLogAndScriptLogLineExecutionLogger(Serializable scriptId, Serializable scriptRevisionId)
{
super(scriptId, scriptRevisionId);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptExecutionStart(ExecuteCodeInput executeCodeInput)
{
try
{
super.acceptExecutionStart(executeCodeInput);
InsertInput insertInput = new InsertInput(executeCodeInput.getInstance());
insertInput.setSession(executeCodeInput.getSession());
insertInput.setTableName("scriptLog");
insertInput.setRecords(List.of(getScriptLog()));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
setScriptLog(insertOutput.getRecords().get(0));
}
catch(Exception e)
{
LOG.warn("Error starting storage of script log", e);
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptException(Exception exception)
{
store(null, exception);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptExecutionEnd(Serializable output)
{
store(output, null);
}
/*******************************************************************************
**
*******************************************************************************/
private void store(Serializable output, Exception exception)
{
try
{
updateHeaderAtEnd(output, exception);
UpdateInput updateInput = new UpdateInput(executeCodeInput.getInstance());
updateInput.setSession(executeCodeInput.getSession());
updateInput.setTableName("scriptLog");
updateInput.setRecords(List.of(getScriptLog()));
new UpdateAction().execute(updateInput);
if(CollectionUtils.nullSafeHasContents(getScriptLogLines()))
{
InsertInput insertInput = new InsertInput(executeCodeInput.getInstance());
insertInput.setSession(executeCodeInput.getSession());
insertInput.setTableName("scriptLogLine");
insertInput.setRecords(getScriptLogLines());
new InsertAction().execute(insertInput);
}
}
catch(Exception e)
{
LOG.warn("Error storing script log", e);
}
}
}

View File

@ -0,0 +1,161 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.tables;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.interfaces.GetInterface;
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.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.data.QRecord;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
/*******************************************************************************
** Action to run a get against a table.
**
*******************************************************************************/
public class GetAction
{
private Optional<Function<QRecord, QRecord>> postGetRecordCustomizer;
private GetInput getInput;
private QValueFormatter qValueFormatter;
private QPossibleValueTranslator qPossibleValueTranslator;
/*******************************************************************************
**
*******************************************************************************/
public GetOutput execute(GetInput getInput) throws QException
{
ActionHelper.validateSession(getInput);
postGetRecordCustomizer = QCodeLoader.getTableCustomizerFunction(getInput.getTable(), TableCustomizers.POST_QUERY_RECORD.getRole());
this.getInput = getInput;
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(getInput.getBackend());
// todo pre-customization - just get to modify the request?
GetInterface getInterface = null;
try
{
getInterface = qModule.getGetInterface();
}
catch(IllegalStateException ise)
{
////////////////////////////////////////////////////////////////////////////////////////////////
// if a module doesn't implement Get directly - try to do a Get by a Query by the primary key //
// see below. //
////////////////////////////////////////////////////////////////////////////////////////////////
}
GetOutput getOutput;
if(getInterface != null)
{
getOutput = getInterface.execute(getInput);
}
else
{
getOutput = performGetViaQuery(getInput);
}
if(getOutput.getRecord() != null)
{
getOutput.setRecord(postRecordActions(getOutput.getRecord()));
}
return getOutput;
}
/*******************************************************************************
**
*******************************************************************************/
private GetOutput performGetViaQuery(GetInput getInput) throws QException
{
QueryInput queryInput = new QueryInput(getInput.getInstance());
queryInput.setSession(getInput.getSession());
queryInput.setTableName(getInput.getTableName());
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(getInput.getTable().getPrimaryKeyField(), QCriteriaOperator.EQUALS, List.of(getInput.getPrimaryKey()))));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
GetOutput getOutput = new GetOutput();
if(!queryOutput.getRecords().isEmpty())
{
getOutput.setRecord(queryOutput.getRecords().get(0));
}
return (getOutput);
}
/*******************************************************************************
** Run the necessary actions on a record. This may include setting display values,
** translating possible values, and running post-record customizations.
*******************************************************************************/
public QRecord postRecordActions(QRecord record)
{
QRecord returnRecord = record;
if(this.postGetRecordCustomizer.isPresent())
{
returnRecord = postGetRecordCustomizer.get().apply(record);
}
if(getInput.getShouldTranslatePossibleValues())
{
if(qPossibleValueTranslator == null)
{
qPossibleValueTranslator = new QPossibleValueTranslator(getInput.getInstance(), getInput.getSession());
}
qPossibleValueTranslator.translatePossibleValuesInRecords(getInput.getTable(), List.of(returnRecord));
}
if(getInput.getShouldGenerateDisplayValues())
{
if(qValueFormatter == null)
{
qValueFormatter = new QValueFormatter();
}
qValueFormatter.setDisplayValuesInRecords(getInput.getTable(), List.of(returnRecord));
}
return (returnRecord);
}
}

View File

@ -406,8 +406,11 @@ public class QPossibleValueTranslator
/////////////////////////////////////////////////////////////////////////////////////////
// this is needed to get record labels, which are what we use here... unclear if best! //
/////////////////////////////////////////////////////////////////////////////////////////
queryInput.setShouldTranslatePossibleValues(true);
queryInput.setShouldGenerateDisplayValues(true);
if(notTooDeep())
{
queryInput.setShouldTranslatePossibleValues(true);
queryInput.setShouldGenerateDisplayValues(true);
}
QueryOutput queryOutput = new QueryAction().execute(queryInput);
@ -428,4 +431,24 @@ public class QPossibleValueTranslator
}
}
/*******************************************************************************
** Avoid infinite recursion, for where one field's PVS depends on another's...
** not too smart, just breaks at 5...
*******************************************************************************/
private boolean notTooDeep()
{
int count = 0;
for(StackTraceElement stackTraceElement : new Throwable().getStackTrace())
{
if(stackTraceElement.getMethodName().equals("translatePossibleValuesInRecords"))
{
count++;
}
}
return (count < 5);
}
}

View File

@ -184,6 +184,7 @@ public class QValueFormatter
}
catch(Exception e)
{
LOG.debug("Error formatting record label", e);
return (formatRecordLabelExceptionalCases(table, record));
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.exceptions;
/*******************************************************************************
** Exception thrown while executing custom code in QQQ.
**
** Context field is meant to give the user "context" for where the error occurred
** - e.g., a line number or word that was bad.
*******************************************************************************/
public class QCodeException extends QException
{
private String context;
/*******************************************************************************
** Constructor of message
**
*******************************************************************************/
public QCodeException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor of message & cause
**
*******************************************************************************/
public QCodeException(String message, Throwable cause)
{
super(message, cause);
}
/*******************************************************************************
** Getter for context
**
*******************************************************************************/
public String getContext()
{
return context;
}
/*******************************************************************************
** Setter for context
**
*******************************************************************************/
public void setContext(String context)
{
this.context = context;
}
}

View File

@ -316,6 +316,24 @@ public class QInstanceEnricher
{
generateAppSections(app);
}
for(QAppSection section : CollectionUtils.nonNullList(app.getSections()))
{
enrichAppSection(section);
}
}
/*******************************************************************************
**
*******************************************************************************/
private void enrichAppSection(QAppSection section)
{
if(!StringUtils.hasContent(section.getLabel()))
{
section.setLabel(nameToLabel(section.getName()));
}
}
@ -590,7 +608,7 @@ public class QInstanceEnricher
** <li>TLAAndAnotherTLA -> tla_and_another_tla</li>
** </ul>
*******************************************************************************/
static String inferBackendName(String fieldName)
public static String inferBackendName(String fieldName)
{
////////////////////////////////////////////////////////////////////////////////////////
// build a list of words in the name, then join them with _ and lower-case the result //

View File

@ -31,13 +31,16 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
@ -50,6 +53,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaD
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.queues.SQSQueueProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
@ -118,6 +123,7 @@ public class QInstanceValidator
validateAutomationProviders(qInstance);
validateTables(qInstance);
validateProcesses(qInstance);
validateReports(qInstance);
validateApps(qInstance);
validatePossibleValueSources(qInstance);
validateQueuesAndProviders(qInstance);
@ -186,6 +192,8 @@ public class QInstanceValidator
qInstance.getBackends().forEach((backendName, backend) ->
{
assertCondition(Objects.equals(backendName, backend.getName()), "Inconsistent naming for backend: " + backendName + "/" + backend.getName() + ".");
backend.performValidation(this);
});
}
}
@ -258,6 +266,8 @@ public class QInstanceValidator
//////////////////////////////////////////
Set<String> fieldNamesInSections = new HashSet<>();
QFieldSection tier1Section = null;
Set<String> usedSectionNames = new HashSet<>();
Set<String> usedSectionLabels = new HashSet<>();
if(table.getSections() != null)
{
for(QFieldSection section : table.getSections())
@ -268,6 +278,12 @@ public class QInstanceValidator
assertCondition(tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1");
tier1Section = section;
}
assertCondition(!usedSectionNames.contains(section.getName()), "Table " + tableName + " has more than 1 section named " + section.getName());
usedSectionNames.add(section.getName());
assertCondition(!usedSectionLabels.contains(section.getLabel()), "Table " + tableName + " has more than 1 section labeled " + section.getLabel());
usedSectionLabels.add(section.getLabel());
}
}
@ -716,6 +732,133 @@ public class QInstanceValidator
/*******************************************************************************
**
*******************************************************************************/
private void validateReports(QInstance qInstance)
{
if(CollectionUtils.nullSafeHasContents(qInstance.getReports()))
{
qInstance.getReports().forEach((reportName, report) ->
{
assertCondition(Objects.equals(reportName, report.getName()), "Inconsistent naming for report: " + reportName + "/" + report.getName() + ".");
validateAppChildHasValidParentAppName(qInstance, report);
////////////////////////////////////////
// validate dataSources in the report //
////////////////////////////////////////
Set<String> usedDataSourceNames = new HashSet<>();
if(assertCondition(CollectionUtils.nullSafeHasContents(report.getDataSources()), "At least 1 data source must be defined in report " + reportName + "."))
{
int index = 0;
for(QReportDataSource dataSource : report.getDataSources())
{
assertCondition(StringUtils.hasContent(dataSource.getName()), "Missing name for a dataSource at index " + index + " in report " + reportName);
index++;
assertCondition(!usedDataSourceNames.contains(dataSource.getName()), "More than one dataSource with name " + dataSource.getName() + " in report " + reportName);
usedDataSourceNames.add(dataSource.getName());
String dataSourceErrorPrefix = "Report " + reportName + " data source " + dataSource.getName() + " ";
if(StringUtils.hasContent(dataSource.getSourceTable()))
{
assertCondition(dataSource.getStaticDataSupplier() == null, dataSourceErrorPrefix + "has both a sourceTable and a staticDataSupplier (exactly 1 is required).");
if(assertCondition(qInstance.getTable(dataSource.getSourceTable()) != null, dataSourceErrorPrefix + "source table " + dataSource.getSourceTable() + " is not a table in this instance."))
{
if(dataSource.getQueryFilter() != null)
{
validateQueryFilter("In " + dataSourceErrorPrefix + "query filter - ", qInstance.getTable(dataSource.getSourceTable()), dataSource.getQueryFilter());
}
}
}
else if(dataSource.getStaticDataSupplier() != null)
{
validateSimpleCodeReference(dataSourceErrorPrefix, dataSource.getStaticDataSupplier(), Supplier.class);
}
else
{
errors.add(dataSourceErrorPrefix + "does not have a sourceTable or a staticDataSupplier (exactly 1 is required).");
}
}
}
////////////////////////////////////////
// validate dataSources in the report //
////////////////////////////////////////
if(assertCondition(CollectionUtils.nullSafeHasContents(report.getViews()), "At least 1 view must be defined in report " + reportName + "."))
{
int index = 0;
Set<String> usedViewNames = new HashSet<>();
for(QReportView view : report.getViews())
{
assertCondition(StringUtils.hasContent(view.getName()), "Missing name for a view at index " + index + " in report " + reportName);
index++;
assertCondition(!usedViewNames.contains(view.getName()), "More than one view with name " + view.getName() + " in report " + reportName);
usedViewNames.add(view.getName());
String viewErrorPrefix = "Report " + reportName + " view " + view.getName() + " ";
assertCondition(view.getType() != null, viewErrorPrefix + " is missing its type.");
if(assertCondition(StringUtils.hasContent(view.getDataSourceName()), viewErrorPrefix + " is missing a dataSourceName"))
{
assertCondition(usedDataSourceNames.contains(view.getDataSourceName()), viewErrorPrefix + " has an unrecognized dataSourceName: " + view.getDataSourceName());
}
if(StringUtils.hasContent(view.getVarianceDataSourceName()))
{
assertCondition(usedDataSourceNames.contains(view.getVarianceDataSourceName()), viewErrorPrefix + " has an unrecognized varianceDataSourceName: " + view.getVarianceDataSourceName());
}
// actually, this is okay if there's a customizer, so...
assertCondition(CollectionUtils.nullSafeHasContents(view.getColumns()), viewErrorPrefix + " does not have any columns.");
// todo - all these too...
// view.getPivotFields();
// view.getViewCustomizer(); // validate code ref
// view.getRecordTransformStep(); // validate code ref
// view.getOrderByFields(); // make sure valid field names?
// view.getIncludePivotSubTotals(); // only for pivot type
// view.getTitleFormat(); view.getTitleFields(); // validate these match?
}
}
});
}
}
/*******************************************************************************
**
*******************************************************************************/
private void validateQueryFilter(String context, QTableMetaData table, QQueryFilter queryFilter)
{
for(QFilterCriteria criterion : CollectionUtils.nonNullList(queryFilter.getCriteria()))
{
if(assertCondition(StringUtils.hasContent(criterion.getFieldName()), context + "Missing fieldName for a criteria"))
{
assertNoException(() -> table.getField(criterion.getFieldName()), context + "Criteria fieldName " + criterion.getFieldName() + " is not a field in this table.");
}
assertCondition(criterion.getOperator() != null, context + "Missing operator for a criteria on fieldName " + criterion.getFieldName());
assertCondition(criterion.getValues() != null, context + "Missing values for a criteria on fieldName " + criterion.getFieldName()); // todo - what about ops w/ no value (BLANK)
}
for(QFilterOrderBy orderBy : CollectionUtils.nonNullList(queryFilter.getOrderBys()))
{
if(assertCondition(StringUtils.hasContent(orderBy.getFieldName()), context + "Missing fieldName for an orderBy"))
{
assertNoException(() -> table.getField(orderBy.getFieldName()), context + "OrderBy fieldName " + orderBy.getFieldName() + " is not a field in this table.");
}
}
for(QQueryFilter subFilter : CollectionUtils.nonNullList(queryFilter.getSubFilters()))
{
validateQueryFilter(context, table, subFilter);
}
}
/*******************************************************************************
**
*******************************************************************************/
@ -978,7 +1121,7 @@ public class QInstanceValidator
** But if it's false, 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 assertCondition(boolean condition, String message)
public boolean assertCondition(boolean condition, String message)
{
if(!condition)
{
@ -1035,4 +1178,15 @@ public class QInstanceValidator
LOG.info("Validation warning: " + message);
}
}
/*******************************************************************************
** Getter for errors
**
*******************************************************************************/
public List<String> getErrors()
{
return errors;
}
}

View File

@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
@ -44,12 +45,14 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
*******************************************************************************/
public class RunBackendStepInput extends AbstractActionInput
{
private ProcessState processState;
private String processName;
private String tableName;
private String stepName;
private QProcessCallback callback;
private AsyncJobCallback asyncJobCallback;
private ProcessState processState;
private String processName;
private String tableName;
private String stepName;
private QProcessCallback callback;
private AsyncJobCallback asyncJobCallback;
private RunProcessInput.FrontendStepBehavior frontendStepBehavior;
private Instant basepullLastRunTime;
////////////////////////////////////////////////////////////////////////////
// note - new fields should generally be added in method: cloneFieldsInto //
@ -416,6 +419,17 @@ public class RunBackendStepInput extends AbstractActionInput
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Instant getValueInstant(String fieldName)
{
return (ValueUtils.getValueAsInstant(getValue(fieldName)));
}
/*******************************************************************************
** Accessor for processState - protected, because we generally want to access
** its members through wrapper methods, we think
@ -453,4 +467,72 @@ public class RunBackendStepInput extends AbstractActionInput
return (asyncJobCallback);
}
/*******************************************************************************
** Getter for frontendStepBehavior
**
*******************************************************************************/
public RunProcessInput.FrontendStepBehavior getFrontendStepBehavior()
{
return frontendStepBehavior;
}
/*******************************************************************************
** Setter for frontendStepBehavior
**
*******************************************************************************/
public void setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior frontendStepBehavior)
{
this.frontendStepBehavior = frontendStepBehavior;
}
/*******************************************************************************
** Fluent setter for frontendStepBehavior
**
*******************************************************************************/
public RunBackendStepInput withFrontendStepBehavior(RunProcessInput.FrontendStepBehavior frontendStepBehavior)
{
this.frontendStepBehavior = frontendStepBehavior;
return (this);
}
/*******************************************************************************
** Getter for basepullLastRunTime
**
*******************************************************************************/
public Instant getBasepullLastRunTime()
{
return basepullLastRunTime;
}
/*******************************************************************************
** Setter for basepullLastRunTime
**
*******************************************************************************/
public void setBasepullLastRunTime(Instant basepullLastRunTime)
{
this.basepullLastRunTime = basepullLastRunTime;
}
/*******************************************************************************
** Fluent setter for basepullLastRunTime
**
*******************************************************************************/
public RunBackendStepInput withBasepullLastRunTime(Instant basepullLastRunTime)
{
this.basepullLastRunTime = basepullLastRunTime;
return (this);
}
}

View File

@ -0,0 +1,223 @@
/*
* 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.actions.scripts;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
/*******************************************************************************
**
*******************************************************************************/
public class ExecuteCodeInput extends AbstractActionInput
{
private QCodeReference codeReference;
private Map<String, Serializable> input;
private Map<String, Serializable> context;
private QCodeExecutionLoggerInterface executionLogger;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ExecuteCodeInput(QInstance qInstance)
{
super(qInstance);
}
/*******************************************************************************
** Getter for codeReference
**
*******************************************************************************/
public QCodeReference getCodeReference()
{
return codeReference;
}
/*******************************************************************************
** Setter for codeReference
**
*******************************************************************************/
public void setCodeReference(QCodeReference codeReference)
{
this.codeReference = codeReference;
}
/*******************************************************************************
** Fluent setter for codeReference
**
*******************************************************************************/
public ExecuteCodeInput withCodeReference(QCodeReference codeReference)
{
this.codeReference = codeReference;
return (this);
}
/*******************************************************************************
** Getter for input
**
*******************************************************************************/
public Map<String, Serializable> getInput()
{
return input;
}
/*******************************************************************************
** Setter for input
**
*******************************************************************************/
public void setInput(Map<String, Serializable> input)
{
this.input = input;
}
/*******************************************************************************
** Fluent setter for input
**
*******************************************************************************/
public ExecuteCodeInput withInput(Map<String, Serializable> input)
{
this.input = input;
return (this);
}
/*******************************************************************************
** Fluent setter for input
**
*******************************************************************************/
public ExecuteCodeInput withInput(String key, Serializable value)
{
if(this.input == null)
{
input = new HashMap<>();
}
this.input.put(key, value);
return (this);
}
/*******************************************************************************
** Getter for context
**
*******************************************************************************/
public Map<String, Serializable> getContext()
{
return context;
}
/*******************************************************************************
** Setter for context
**
*******************************************************************************/
public void setContext(Map<String, Serializable> context)
{
this.context = context;
}
/*******************************************************************************
** Fluent setter for context
**
*******************************************************************************/
public ExecuteCodeInput withContext(Map<String, Serializable> context)
{
this.context = context;
return (this);
}
/*******************************************************************************
** Fluent setter for context
**
*******************************************************************************/
public ExecuteCodeInput withContext(String key, Serializable value)
{
if(this.context == null)
{
context = new HashMap<>();
}
this.context.put(key, value);
return (this);
}
/*******************************************************************************
** Getter for executionLogger
**
*******************************************************************************/
public QCodeExecutionLoggerInterface getExecutionLogger()
{
return executionLogger;
}
/*******************************************************************************
** Setter for executionLogger
**
*******************************************************************************/
public void setExecutionLogger(QCodeExecutionLoggerInterface executionLogger)
{
this.executionLogger = executionLogger;
}
/*******************************************************************************
** Fluent setter for executionLogger
**
*******************************************************************************/
public ExecuteCodeInput withExecutionLogger(QCodeExecutionLoggerInterface executionLogger)
{
this.executionLogger = executionLogger;
return (this);
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.actions.scripts;
import java.io.Serializable;
/*******************************************************************************
**
*******************************************************************************/
public class ExecuteCodeOutput
{
private Serializable output;
/*******************************************************************************
** Getter for output
**
*******************************************************************************/
public Serializable getOutput()
{
return output;
}
/*******************************************************************************
** Setter for output
**
*******************************************************************************/
public void setOutput(Serializable output)
{
this.output = output;
}
/*******************************************************************************
** Fluent setter for output
**
*******************************************************************************/
public ExecuteCodeOutput withOutput(Serializable output)
{
this.output = output;
return (this);
}
}

View File

@ -0,0 +1,154 @@
/*
* 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.actions.scripts;
import java.io.Serializable;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference;
/*******************************************************************************
**
*******************************************************************************/
public class RunAssociatedScriptInput extends AbstractTableActionInput
{
private AssociatedScriptCodeReference codeReference;
private Map<String, Serializable> inputValues;
private Serializable outputObject;
/*******************************************************************************
**
*******************************************************************************/
public RunAssociatedScriptInput(QInstance qInstance)
{
super(qInstance);
}
/*******************************************************************************
** Getter for codeReference
**
*******************************************************************************/
public AssociatedScriptCodeReference getCodeReference()
{
return codeReference;
}
/*******************************************************************************
** Setter for codeReference
**
*******************************************************************************/
public void setCodeReference(AssociatedScriptCodeReference codeReference)
{
this.codeReference = codeReference;
}
/*******************************************************************************
** Fluent setter for codeReference
**
*******************************************************************************/
public RunAssociatedScriptInput withCodeReference(AssociatedScriptCodeReference codeReference)
{
this.codeReference = codeReference;
return (this);
}
/*******************************************************************************
** Getter for inputValues
**
*******************************************************************************/
public Map<String, Serializable> getInputValues()
{
return inputValues;
}
/*******************************************************************************
** Setter for inputValues
**
*******************************************************************************/
public void setInputValues(Map<String, Serializable> inputValues)
{
this.inputValues = inputValues;
}
/*******************************************************************************
** Fluent setter for inputValues
**
*******************************************************************************/
public RunAssociatedScriptInput withInputValues(Map<String, Serializable> inputValues)
{
this.inputValues = inputValues;
return (this);
}
/*******************************************************************************
** Getter for outputObject
**
*******************************************************************************/
public Serializable getOutputObject()
{
return outputObject;
}
/*******************************************************************************
** Setter for outputObject
**
*******************************************************************************/
public void setOutputObject(Serializable outputObject)
{
this.outputObject = outputObject;
}
/*******************************************************************************
** Fluent setter for outputObject
**
*******************************************************************************/
public RunAssociatedScriptInput withOutputObject(Serializable outputObject)
{
this.outputObject = outputObject;
return (this);
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.actions.scripts;
import java.io.Serializable;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
/*******************************************************************************
**
*******************************************************************************/
public class RunAssociatedScriptOutput extends AbstractActionOutput
{
private Serializable output;
/*******************************************************************************
** Getter for output
**
*******************************************************************************/
public Serializable getOutput()
{
return output;
}
/*******************************************************************************
** Setter for output
**
*******************************************************************************/
public void setOutput(Serializable output)
{
this.output = output;
}
/*******************************************************************************
** Fluent setter for output
**
*******************************************************************************/
public RunAssociatedScriptOutput withOutput(Serializable output)
{
this.output = output;
return (this);
}
}

View File

@ -0,0 +1,188 @@
/*
* 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.actions.scripts;
import java.io.Serializable;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
**
*******************************************************************************/
public class StoreAssociatedScriptInput extends AbstractTableActionInput
{
private String fieldName;
private Serializable recordPrimaryKey;
private String code;
private String commitMessage;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public StoreAssociatedScriptInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
** Getter for fieldName
**
*******************************************************************************/
public String getFieldName()
{
return fieldName;
}
/*******************************************************************************
** Setter for fieldName
**
*******************************************************************************/
public void setFieldName(String fieldName)
{
this.fieldName = fieldName;
}
/*******************************************************************************
** Fluent setter for fieldName
**
*******************************************************************************/
public StoreAssociatedScriptInput withFieldName(String fieldName)
{
this.fieldName = fieldName;
return (this);
}
/*******************************************************************************
** Getter for recordPrimaryKey
**
*******************************************************************************/
public Serializable getRecordPrimaryKey()
{
return recordPrimaryKey;
}
/*******************************************************************************
** Setter for recordPrimaryKey
**
*******************************************************************************/
public void setRecordPrimaryKey(Serializable recordPrimaryKey)
{
this.recordPrimaryKey = recordPrimaryKey;
}
/*******************************************************************************
** Fluent setter for recordPrimaryKey
**
*******************************************************************************/
public StoreAssociatedScriptInput withRecordPrimaryKey(Serializable recordPrimaryKey)
{
this.recordPrimaryKey = recordPrimaryKey;
return (this);
}
/*******************************************************************************
** Getter for code
**
*******************************************************************************/
public String getCode()
{
return code;
}
/*******************************************************************************
** Setter for code
**
*******************************************************************************/
public void setCode(String code)
{
this.code = code;
}
/*******************************************************************************
** Fluent setter for code
**
*******************************************************************************/
public StoreAssociatedScriptInput withCode(String code)
{
this.code = code;
return (this);
}
/*******************************************************************************
** Getter for commitMessage
**
*******************************************************************************/
public String getCommitMessage()
{
return commitMessage;
}
/*******************************************************************************
** Setter for commitMessage
**
*******************************************************************************/
public void setCommitMessage(String commitMessage)
{
this.commitMessage = commitMessage;
}
/*******************************************************************************
** Fluent setter for commitMessage
**
*******************************************************************************/
public StoreAssociatedScriptInput withCommitMessage(String commitMessage)
{
this.commitMessage = commitMessage;
return (this);
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.actions.scripts;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
/*******************************************************************************
**
*******************************************************************************/
public class StoreAssociatedScriptOutput extends AbstractActionOutput
{
}

View File

@ -0,0 +1,187 @@
/*
* 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.actions.scripts;
import java.io.Serializable;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
**
*******************************************************************************/
public class TestScriptInput extends AbstractTableActionInput
{
private Serializable recordPrimaryKey;
private String code;
private Serializable scriptTypeId;
private Map<String, String> inputValues;
/*******************************************************************************
**
*******************************************************************************/
public TestScriptInput(QInstance qInstance)
{
super(qInstance);
}
/*******************************************************************************
** Getter for recordPrimaryKey
**
*******************************************************************************/
public Serializable getRecordPrimaryKey()
{
return recordPrimaryKey;
}
/*******************************************************************************
** Setter for recordPrimaryKey
**
*******************************************************************************/
public void setRecordPrimaryKey(Serializable recordPrimaryKey)
{
this.recordPrimaryKey = recordPrimaryKey;
}
/*******************************************************************************
** Fluent setter for recordPrimaryKey
**
*******************************************************************************/
public TestScriptInput withRecordPrimaryKey(Serializable recordPrimaryKey)
{
this.recordPrimaryKey = recordPrimaryKey;
return (this);
}
/*******************************************************************************
** Getter for inputValues
**
*******************************************************************************/
public Map<String, String> getInputValues()
{
return inputValues;
}
/*******************************************************************************
** Setter for inputValues
**
*******************************************************************************/
public void setInputValues(Map<String, String> inputValues)
{
this.inputValues = inputValues;
}
/*******************************************************************************
** Fluent setter for inputValues
**
*******************************************************************************/
public TestScriptInput withInputValues(Map<String, String> inputValues)
{
this.inputValues = inputValues;
return (this);
}
/*******************************************************************************
** Getter for code
**
*******************************************************************************/
public String getCode()
{
return code;
}
/*******************************************************************************
** Setter for code
**
*******************************************************************************/
public void setCode(String code)
{
this.code = code;
}
/*******************************************************************************
** Fluent setter for code
**
*******************************************************************************/
public TestScriptInput withCode(String code)
{
this.code = code;
return (this);
}
/*******************************************************************************
** Getter for scriptTypeId
**
*******************************************************************************/
public Serializable getScriptTypeId()
{
return scriptTypeId;
}
/*******************************************************************************
** Setter for scriptTypeId
**
*******************************************************************************/
public void setScriptTypeId(Serializable scriptTypeId)
{
this.scriptTypeId = scriptTypeId;
}
/*******************************************************************************
** Fluent setter for scriptTypeId
**
*******************************************************************************/
public TestScriptInput withScriptTypeId(Serializable scriptTypeId)
{
this.scriptTypeId = scriptTypeId;
return (this);
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.actions.scripts;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
/*******************************************************************************
**
*******************************************************************************/
public class TestScriptOutput extends AbstractActionOutput
{
}

View File

@ -0,0 +1,186 @@
/*
* 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.actions.tables.get;
import java.io.Serializable;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession;
/*******************************************************************************
** Input data for the Get action
**
*******************************************************************************/
public class GetInput extends AbstractTableActionInput
{
private QBackendTransaction transaction;
private Serializable primaryKey;
private boolean shouldTranslatePossibleValues = false;
private boolean shouldGenerateDisplayValues = false;
/*******************************************************************************
**
*******************************************************************************/
public GetInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public GetInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
**
*******************************************************************************/
public GetInput(QInstance instance, QSession session)
{
super(instance);
setSession(session);
}
/*******************************************************************************
** Getter for primaryKey
**
*******************************************************************************/
public Serializable getPrimaryKey()
{
return primaryKey;
}
/*******************************************************************************
** Setter for primaryKey
**
*******************************************************************************/
public void setPrimaryKey(Serializable primaryKey)
{
this.primaryKey = primaryKey;
}
/*******************************************************************************
** Fluent setter for primaryKey
**
*******************************************************************************/
public GetInput withPrimaryKey(Serializable primaryKey)
{
this.primaryKey = primaryKey;
return (this);
}
/*******************************************************************************
** Getter for shouldTranslatePossibleValues
**
*******************************************************************************/
public boolean getShouldTranslatePossibleValues()
{
return shouldTranslatePossibleValues;
}
/*******************************************************************************
** Setter for shouldTranslatePossibleValues
**
*******************************************************************************/
public void setShouldTranslatePossibleValues(boolean shouldTranslatePossibleValues)
{
this.shouldTranslatePossibleValues = shouldTranslatePossibleValues;
}
/*******************************************************************************
** Getter for shouldGenerateDisplayValues
**
*******************************************************************************/
public boolean getShouldGenerateDisplayValues()
{
return shouldGenerateDisplayValues;
}
/*******************************************************************************
** Setter for shouldGenerateDisplayValues
**
*******************************************************************************/
public void setShouldGenerateDisplayValues(boolean shouldGenerateDisplayValues)
{
this.shouldGenerateDisplayValues = shouldGenerateDisplayValues;
}
/*******************************************************************************
** Getter for transaction
**
*******************************************************************************/
public QBackendTransaction getTransaction()
{
return transaction;
}
/*******************************************************************************
** Setter for transaction
**
*******************************************************************************/
public void setTransaction(QBackendTransaction transaction)
{
this.transaction = transaction;
}
/*******************************************************************************
** Fluent setter for transaction
**
*******************************************************************************/
public GetInput withTransaction(QBackendTransaction transaction)
{
this.transaction = transaction;
return (this);
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.actions.tables.get;
import java.io.Serializable;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Output for a Get action
**
*******************************************************************************/
public class GetOutput extends AbstractActionOutput implements Serializable
{
private QRecord record;
/*******************************************************************************
** Getter for record
**
*******************************************************************************/
public QRecord getRecord()
{
return record;
}
/*******************************************************************************
** Setter for record
**
*******************************************************************************/
public void setRecord(QRecord record)
{
this.record = record;
}
/*******************************************************************************
** Fluent setter for record
**
*******************************************************************************/
public GetOutput withRecord(QRecord record)
{
this.record = record;
return (this);
}
}

View File

@ -25,6 +25,9 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
@ -33,6 +36,8 @@ import java.util.List;
*******************************************************************************/
public class QFilterCriteria implements Serializable, Cloneable
{
private static final Logger LOG = LogManager.getLogger(QFilterCriteria.class);
private String fieldName;
private QCriteriaOperator operator;
private List<Serializable> values;
@ -183,4 +188,46 @@ public class QFilterCriteria implements Serializable, Cloneable
this.values = values;
return this;
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String toString()
{
StringBuilder rs = new StringBuilder(fieldName);
try
{
rs.append(" ").append(operator).append(" ");
if(CollectionUtils.nullSafeHasContents(values))
{
if(values.size() == 1)
{
rs.append(values.get(0));
}
else
{
int index = 0;
for(Serializable value : values)
{
if(index++ > 9)
{
rs.append("and ").append(values.size() - index).append(" more");
break;
}
rs.append(value).append(",");
}
}
}
}
catch(Exception e)
{
LOG.warn("Error in toString", e);
rs.append("Error generating toString...");
}
return (rs.toString());
}
}

View File

@ -151,4 +151,14 @@ public class QFilterOrderBy implements Serializable, Cloneable
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String toString()
{
return (fieldName + " " + (isAscending ? "ASC" : "DESC"));
}
}

View File

@ -26,6 +26,8 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
@ -34,6 +36,8 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
*******************************************************************************/
public class QQueryFilter implements Serializable, Cloneable
{
private static final Logger LOG = LogManager.getLogger(QQueryFilter.class);
private List<QFilterCriteria> criteria = new ArrayList<>();
private List<QFilterOrderBy> orderBys = new ArrayList<>();
@ -301,4 +305,41 @@ public class QQueryFilter implements Serializable, Cloneable
subFilters.add(subFilter);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String toString()
{
StringBuilder rs = new StringBuilder("(");
try
{
for(QFilterCriteria criterion : CollectionUtils.nonNullList(criteria))
{
rs.append(criterion).append(" ").append(getBooleanOperator());
}
for(QQueryFilter subFilter : CollectionUtils.nonNullList(subFilters))
{
rs.append(subFilter);
}
rs.append(")");
rs.append("OrderBy[");
for(QFilterOrderBy orderBy : orderBys)
{
rs.append(orderBy).append(",");
}
rs.append("]");
}
catch(Exception e)
{
LOG.warn("Error in toString", e);
rs.append("Error generating toString...");
}
return (rs.toString());
}
}

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.model.actions.tables.update;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -54,4 +55,18 @@ public class UpdateOutput extends AbstractActionOutput
{
this.records = records;
}
/*******************************************************************************
**
*******************************************************************************/
public void addRecord(QRecord record)
{
if(this.records == null)
{
this.records = new ArrayList<>();
}
this.records.add(record);
}
}

View File

@ -64,6 +64,8 @@ public class QRecord implements Serializable
private Map<String, Serializable> backendDetails = new LinkedHashMap<>();
private List<String> errors = new ArrayList<>();
public static final String BACKEND_DETAILS_TYPE_JSON_SOURCE_OBJECT = "jsonSourceObject";
/*******************************************************************************
@ -455,6 +457,11 @@ public class QRecord implements Serializable
*******************************************************************************/
public Serializable getBackendDetail(String key)
{
if(!this.backendDetails.containsKey(key))
{
return (null);
}
return this.backendDetails.get(key);
}
@ -466,7 +473,7 @@ public class QRecord implements Serializable
*******************************************************************************/
public String getBackendDetailString(String key)
{
return (String) this.backendDetails.get(key);
return (String) getBackendDetail(key);
}

View File

@ -0,0 +1,175 @@
/*
* 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.data;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
/*******************************************************************************
** Base class for enums that are interoperable with QRecords.
*******************************************************************************/
public interface QRecordEnum
{
ListingHash<Class<? extends QRecordEnum>, QRecordEntityField> fieldMapping = new ListingHash<>();
/*******************************************************************************
** Convert this entity to a QRecord.
**
*******************************************************************************/
default QRecord toQRecord() throws QException
{
try
{
QRecord qRecord = new QRecord();
List<QRecordEntityField> fieldList = getFieldList(this.getClass());
for(QRecordEntityField qRecordEntityField : fieldList)
{
qRecord.setValue(qRecordEntityField.getFieldName(), (Serializable) qRecordEntityField.getGetter().invoke(this));
}
return (qRecord);
}
catch(Exception e)
{
throw (new QException("Error building qRecord from entity.", e));
}
}
/*******************************************************************************
**
*******************************************************************************/
public static List<QRecordEntityField> getFieldList(Class<? extends QRecordEnum> c)
{
if(!fieldMapping.containsKey(c))
{
List<QRecordEntityField> fieldList = new ArrayList<>();
for(Method possibleGetter : c.getMethods())
{
if(isGetter(possibleGetter))
{
String fieldName = getFieldNameFromGetter(possibleGetter);
Optional<QField> fieldAnnotation = getQFieldAnnotation(c, fieldName);
fieldList.add(new QRecordEntityField(fieldName, possibleGetter, null, possibleGetter.getReturnType(), fieldAnnotation.orElse(null)));
}
}
fieldMapping.put(c, fieldList);
}
return (fieldMapping.get(c));
}
/*******************************************************************************
**
*******************************************************************************/
public static Optional<QField> getQFieldAnnotation(Class<? extends QRecordEnum> c, String fieldName)
{
try
{
Field field = c.getDeclaredField(fieldName);
return (Optional.ofNullable(field.getAnnotation(QField.class)));
}
catch(NoSuchFieldException e)
{
//////////////////////////////////////////
// ok, we just won't have an annotation //
//////////////////////////////////////////
}
return (Optional.empty());
}
/*******************************************************************************
**
*******************************************************************************/
public static String getFieldNameFromGetter(Method getter)
{
String nameWithoutGet = getter.getName().replaceFirst("^get", "");
if(nameWithoutGet.length() == 1)
{
return (nameWithoutGet.toLowerCase(Locale.ROOT));
}
return (nameWithoutGet.substring(0, 1).toLowerCase(Locale.ROOT) + nameWithoutGet.substring(1));
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean isGetter(Method method)
{
if(method.getParameterTypes().length == 0 && method.getName().matches("^get[A-Z].*"))
{
if(isSupportedFieldType(method.getReturnType()))
{
return (true);
}
else
{
if(!method.getName().equals("getClass"))
{
System.err.println("Method [" + method.getName() + "] looks like a getter, but its return type, [" + method.getReturnType() + "], isn't supported.");
}
}
}
return (false);
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean isSupportedFieldType(Class<?> returnType)
{
// todo - more types!!
return (returnType.equals(String.class)
|| returnType.equals(Integer.class)
|| returnType.equals(int.class)
|| returnType.equals(Boolean.class)
|| returnType.equals(boolean.class)
|| returnType.equals(BigDecimal.class)
|| returnType.equals(Instant.class)
|| returnType.equals(LocalDate.class)
|| returnType.equals(LocalTime.class));
/////////////////////////////////////////////
// note - this list has implications upon: //
// - QFieldType.fromClass //
// - QRecordEntityField.convertValueType //
/////////////////////////////////////////////
}
}

View File

@ -22,8 +22,13 @@
package com.kingsrook.qqq.backend.core.model.metadata;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.serialization.QBackendMetaDataDeserializer;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
@ -38,6 +43,9 @@ public class QBackendMetaData
private String name;
private String backendType;
private Set<Capability> enabledCapabilities = new HashSet<>();
private Set<Capability> disabledCapabilities = new HashSet<>();
// todo - at some point, we may want to apply this to secret properties on subclasses?
// @JsonFilter("secretsFilter")
@ -157,4 +165,172 @@ public class QBackendMetaData
// noop in base class //
////////////////////////
}
/*******************************************************************************
** Getter for enabledCapabilities
**
*******************************************************************************/
public Set<Capability> getEnabledCapabilities()
{
return enabledCapabilities;
}
/*******************************************************************************
** Setter for enabledCapabilities
**
*******************************************************************************/
public void setEnabledCapabilities(Set<Capability> enabledCapabilities)
{
this.enabledCapabilities = enabledCapabilities;
}
/*******************************************************************************
** Fluent setter for enabledCapabilities
**
*******************************************************************************/
public QBackendMetaData withEnabledCapabilities(Set<Capability> enabledCapabilities)
{
this.enabledCapabilities = enabledCapabilities;
return (this);
}
/*******************************************************************************
** Alternative fluent setter for enabledCapabilities
**
*******************************************************************************/
public QBackendMetaData withCapabilities(Set<Capability> enabledCapabilities)
{
this.enabledCapabilities = enabledCapabilities;
return (this);
}
/*******************************************************************************
** Alternative fluent setter for a single enabledCapabilities
**
*******************************************************************************/
public QBackendMetaData withCapability(Capability capability)
{
if(this.enabledCapabilities == null)
{
this.enabledCapabilities = new HashSet<>();
}
this.enabledCapabilities.add(capability);
return (this);
}
/*******************************************************************************
** Fluent setter for enabledCapabilities
**
*******************************************************************************/
public QBackendMetaData withCapabilities(Capability... enabledCapabilities)
{
if(this.enabledCapabilities == null)
{
this.enabledCapabilities = new HashSet<>();
}
this.enabledCapabilities.addAll(Arrays.stream(enabledCapabilities).toList());
return (this);
}
/*******************************************************************************
** Getter for disabledCapabilities
**
*******************************************************************************/
public Set<Capability> getDisabledCapabilities()
{
return disabledCapabilities;
}
/*******************************************************************************
** Setter for disabledCapabilities
**
*******************************************************************************/
public void setDisabledCapabilities(Set<Capability> disabledCapabilities)
{
this.disabledCapabilities = disabledCapabilities;
}
/*******************************************************************************
** Fluent setter for disabledCapabilities
**
*******************************************************************************/
public QBackendMetaData withDisabledCapabilities(Set<Capability> disabledCapabilities)
{
this.disabledCapabilities = disabledCapabilities;
return (this);
}
/*******************************************************************************
** Fluent setter for disabledCapabilities
**
*******************************************************************************/
public QBackendMetaData withoutCapabilities(Capability... disabledCapabilities)
{
if(this.disabledCapabilities == null)
{
this.disabledCapabilities = new HashSet<>();
}
this.disabledCapabilities.addAll(Arrays.stream(disabledCapabilities).toList());
return (this);
}
/*******************************************************************************
** Alternative fluent setter for disabledCapabilities
**
*******************************************************************************/
public QBackendMetaData withoutCapabilities(Set<Capability> disabledCapabilities)
{
this.disabledCapabilities = disabledCapabilities;
return (this);
}
/*******************************************************************************
** Alternative fluent setter for a single disabledCapabilities
**
*******************************************************************************/
public QBackendMetaData withoutCapability(Capability capability)
{
if(this.disabledCapabilities == null)
{
this.disabledCapabilities = new HashSet<>();
}
this.disabledCapabilities.add(capability);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public void performValidation(QInstanceValidator qInstanceValidator)
{
////////////////////////
// noop in base class //
////////////////////////
}
}

View File

@ -0,0 +1,139 @@
/*
* 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.metadata.code;
import java.io.Serializable;
/*******************************************************************************
**
*******************************************************************************/
public class AssociatedScriptCodeReference extends QCodeReference
{
private String recordTable;
private Serializable recordPrimaryKey;
private String fieldName;
/*******************************************************************************
** Getter for recordTable
**
*******************************************************************************/
public String getRecordTable()
{
return recordTable;
}
/*******************************************************************************
** Setter for recordTable
**
*******************************************************************************/
public void setRecordTable(String recordTable)
{
this.recordTable = recordTable;
}
/*******************************************************************************
** Fluent setter for recordTable
**
*******************************************************************************/
public AssociatedScriptCodeReference withRecordTable(String recordTable)
{
this.recordTable = recordTable;
return (this);
}
/*******************************************************************************
** Getter for recordPrimaryKey
**
*******************************************************************************/
public Serializable getRecordPrimaryKey()
{
return recordPrimaryKey;
}
/*******************************************************************************
** Setter for recordPrimaryKey
**
*******************************************************************************/
public void setRecordPrimaryKey(Serializable recordPrimaryKey)
{
this.recordPrimaryKey = recordPrimaryKey;
}
/*******************************************************************************
** Fluent setter for recordPrimaryKey
**
*******************************************************************************/
public AssociatedScriptCodeReference withRecordPrimaryKey(Serializable recordPrimaryKey)
{
this.recordPrimaryKey = recordPrimaryKey;
return (this);
}
/*******************************************************************************
** Getter for fieldName
**
*******************************************************************************/
public String getFieldName()
{
return fieldName;
}
/*******************************************************************************
** Setter for fieldName
**
*******************************************************************************/
public void setFieldName(String fieldName)
{
this.fieldName = fieldName;
}
/*******************************************************************************
** Fluent setter for fieldName
**
*******************************************************************************/
public AssociatedScriptCodeReference withFieldName(String fieldName)
{
this.fieldName = fieldName;
return (this);
}
}

View File

@ -38,6 +38,8 @@ public class QCodeReference implements Serializable
private QCodeType codeType;
private QCodeUsage codeUsage;
private String inlineCode;
/*******************************************************************************
@ -212,4 +214,38 @@ public class QCodeReference implements Serializable
return (this);
}
/*******************************************************************************
** Getter for inlineCode
**
*******************************************************************************/
public String getInlineCode()
{
return inlineCode;
}
/*******************************************************************************
** Setter for inlineCode
**
*******************************************************************************/
public void setInlineCode(String inlineCode)
{
this.inlineCode = inlineCode;
}
/*******************************************************************************
** Fluent setter for inlineCode
**
*******************************************************************************/
public QCodeReference withInlineCode(String inlineCode)
{
this.inlineCode = inlineCode;
return (this);
}
}

View File

@ -28,5 +28,6 @@ package com.kingsrook.qqq.backend.core.model.metadata.code;
*******************************************************************************/
public enum QCodeType
{
JAVA
JAVA,
JAVA_SCRIPT
}

View File

@ -22,6 +22,9 @@
package com.kingsrook.qqq.backend.core.model.metadata.dashboard;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
@ -38,6 +41,8 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
protected Integer gridColumns;
protected QCodeReference codeReference;
protected Map<String, Serializable> defaultValues = new LinkedHashMap<>();
/*******************************************************************************
@ -242,4 +247,56 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
return (this);
}
/*******************************************************************************
** Getter for defaultValues
**
*******************************************************************************/
public Map<String, Serializable> getDefaultValues()
{
return defaultValues;
}
/*******************************************************************************
** Setter for defaultValues
**
*******************************************************************************/
public void setDefaultValues(Map<String, Serializable> defaultValues)
{
this.defaultValues = defaultValues;
}
/*******************************************************************************
** Fluent setter for defaultValues
**
*******************************************************************************/
public QWidgetMetaData withDefaultValues(Map<String, Serializable> defaultValues)
{
this.defaultValues = defaultValues;
return (this);
}
/*******************************************************************************
** Fluent setter for a single defaultValue
**
*******************************************************************************/
public QWidgetMetaData withDefaultValue(String key, Serializable value)
{
if(this.defaultValues == null)
{
this.defaultValues = new LinkedHashMap<>();
}
this.defaultValues.put(key, value);
return (this);
}
}

View File

@ -34,7 +34,11 @@ public enum AdornmentType
{
LINK,
CHIP,
SIZE;
SIZE,
CODE_EDITOR;
//////////////////////////////////////////////////////////////////////////
// keep these values in sync with AdornmentType.ts in qqq-frontend-core //
//////////////////////////////////////////////////////////////////////////

View File

@ -23,11 +23,16 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -53,6 +58,8 @@ public class QFrontendTableMetaData
private List<String> widgets;
private Set<String> capabilities;
//////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! //
//////////////////////////////////////////////////////////////////////////////////
@ -62,7 +69,7 @@ public class QFrontendTableMetaData
/*******************************************************************************
**
*******************************************************************************/
public QFrontendTableMetaData(QTableMetaData tableMetaData, boolean includeFields)
public QFrontendTableMetaData(QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFields)
{
this.name = tableMetaData.getName();
this.label = tableMetaData.getLabel();
@ -89,6 +96,62 @@ public class QFrontendTableMetaData
{
this.widgets = tableMetaData.getWidgets();
}
setCapabilities(backendForTable, tableMetaData);
}
/*******************************************************************************
**
*******************************************************************************/
private void setCapabilities(QBackendMetaData backend, QTableMetaData table)
{
Set<Capability> enabledCapabilities = new HashSet<>();
for(Capability capability : Capability.values())
{
///////////////////////////////////////////////
// by default, every table can do everything //
///////////////////////////////////////////////
boolean hasCapability = true;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if the table's backend says the capability is disabled, then by default, then the capability is disabled... //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(backend.getDisabledCapabilities().contains(capability))
{
hasCapability = false;
/////////////////////////////////////////////////////////////////
// unless the table overrides that and says that it IS enabled //
/////////////////////////////////////////////////////////////////
if(table.getEnabledCapabilities().contains(capability))
{
hasCapability = true;
}
}
else
{
/////////////////////////////////////////////////////////////////////////////////////////
// if the backend doesn't specify the capability, then disable it if the table says so //
/////////////////////////////////////////////////////////////////////////////////////////
if(table.getDisabledCapabilities().contains(capability))
{
hasCapability = false;
}
}
if(hasCapability)
{
///////////////////////////////////////
// todo - check if user is allowed!! //
///////////////////////////////////////
enabledCapabilities.add(capability);
}
}
this.capabilities = enabledCapabilities.stream().map(Enum::name).collect(Collectors.toSet());
}
@ -178,4 +241,16 @@ public class QFrontendTableMetaData
{
return widgets;
}
/*******************************************************************************
** Getter for capabilities
**
*******************************************************************************/
public Set<String> getCapabilities()
{
return capabilities;
}
}

View File

@ -24,6 +24,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -340,4 +343,38 @@ public class QAppMetaData implements QAppChildMetaData
this.addSection(section);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public QAppMetaData withSectionOfChildren(QAppSection section, QAppChildMetaData... children)
{
this.addSection(section);
for(QAppChildMetaData child : children)
{
withChild(child);
if(child instanceof QTableMetaData)
{
section.withTable(child.getName());
}
else if(child instanceof QProcessMetaData)
{
section.withProcess(child.getName());
}
else if(child instanceof QReportMetaData)
{
section.withReport(child.getName());
}
else
{
throw new IllegalArgumentException("Unrecognized child type: " + child.getName());
}
}
return (this);
}
}

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.model.metadata.layout;
import java.util.ArrayList;
import java.util.List;
@ -166,6 +167,22 @@ public class QAppSection
/*******************************************************************************
** Fluent setter for tables
**
*******************************************************************************/
public QAppSection withTable(String tableName)
{
if(this.tables == null)
{
this.tables = new ArrayList<>();
}
this.tables.add(tableName);
return (this);
}
/*******************************************************************************
** Getter for processes
**
@ -200,6 +217,22 @@ public class QAppSection
/*******************************************************************************
** Fluent setter for processes
**
*******************************************************************************/
public QAppSection withProcess(String processName)
{
if(this.processes == null)
{
this.processes = new ArrayList<>();
}
this.processes.add(processName);
return (this);
}
/*******************************************************************************
** Getter for reports
**
@ -234,6 +267,22 @@ public class QAppSection
/*******************************************************************************
** Fluent setter for reports
**
*******************************************************************************/
public QAppSection withReport(String reportName)
{
if(this.reports == null)
{
this.reports = new ArrayList<>();
}
this.reports.add(reportName);
return (this);
}
/*******************************************************************************
** Getter for icon
**

View File

@ -34,6 +34,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
public class QIcon
{
private String name;
private String path;
@ -88,4 +89,38 @@ public class QIcon
return (this);
}
/*******************************************************************************
** Getter for path
**
*******************************************************************************/
public String getPath()
{
return path;
}
/*******************************************************************************
** Setter for path
**
*******************************************************************************/
public void setPath(String path)
{
this.path = path;
}
/*******************************************************************************
** Fluent setter for path
**
*******************************************************************************/
public QIcon withPath(String path)
{
this.path = path;
return (this);
}
}

View File

@ -289,6 +289,7 @@ public class QPossibleValueSource
*******************************************************************************/
public void setTableName(String tableName)
{
this.type = QPossibleValueSourceType.TABLE;
this.tableName = tableName;
}
@ -300,7 +301,7 @@ public class QPossibleValueSource
*******************************************************************************/
public QPossibleValueSource withTableName(String tableName)
{
this.tableName = tableName;
setTableName(tableName);
return (this);
}
@ -446,6 +447,7 @@ public class QPossibleValueSource
public void setEnumValues(List<QPossibleValue<?>> enumValues)
{
this.enumValues = enumValues;
setType(QPossibleValueSourceType.ENUM);
}
@ -456,7 +458,7 @@ public class QPossibleValueSource
*******************************************************************************/
public QPossibleValueSource withEnumValues(List<QPossibleValue<?>> enumValues)
{
this.enumValues = enumValues;
setEnumValues(enumValues);
return this;
}
@ -472,6 +474,7 @@ public class QPossibleValueSource
this.enumValues = new ArrayList<>();
}
this.enumValues.add(possibleValue);
setType(QPossibleValueSourceType.ENUM);
}
@ -512,6 +515,7 @@ public class QPossibleValueSource
public void setCustomCodeReference(QCodeReference customCodeReference)
{
this.customCodeReference = customCodeReference;
setType(QPossibleValueSourceType.CUSTOM);
}
@ -522,7 +526,7 @@ public class QPossibleValueSource
*******************************************************************************/
public QPossibleValueSource withCustomCodeReference(QCodeReference customCodeReference)
{
this.customCodeReference = customCodeReference;
setCustomCodeReference(customCodeReference);
return (this);
}

View File

@ -0,0 +1,68 @@
/*
* 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.metadata.processes;
import java.io.Serializable;
/*******************************************************************************
**
*******************************************************************************/
public class AbstractProcessMetaDataBuilder
{
protected QProcessMetaData processMetaData;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public AbstractProcessMetaDataBuilder(QProcessMetaData processMetaData)
{
this.processMetaData = processMetaData;
}
/*******************************************************************************
** Getter for processMetaData
**
*******************************************************************************/
public QProcessMetaData getProcessMetaData()
{
return processMetaData;
}
/*******************************************************************************
**
*******************************************************************************/
protected void setInputFieldDefaultValue(String fieldName, Serializable value)
{
processMetaData.getInputFields().stream()
.filter(f -> f.getName().equals(fieldName)).findFirst()
.ifPresent(f -> f.setDefaultValue(value));
}
}

View File

@ -33,6 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
/*******************************************************************************
@ -41,10 +42,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaDa
*******************************************************************************/
public class QProcessMetaData implements QAppChildMetaData
{
private String name;
private String label;
private String tableName;
private boolean isHidden = false;
private String name;
private String label;
private String tableName;
private boolean isHidden = false;
private BasepullConfiguration basepullConfiguration;
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
@ -473,4 +475,38 @@ public class QProcessMetaData implements QAppChildMetaData
return (this);
}
/*******************************************************************************
** Getter for basepullConfiguration
**
*******************************************************************************/
public BasepullConfiguration getBasepullConfiguration()
{
return basepullConfiguration;
}
/*******************************************************************************
** Setter for basepullConfiguration
**
*******************************************************************************/
public void setBasepullConfiguration(BasepullConfiguration basepullConfiguration)
{
this.basepullConfiguration = basepullConfiguration;
}
/*******************************************************************************
** Fluent setter for basepullConfiguration
**
*******************************************************************************/
public QProcessMetaData withBasepullConfiguration(BasepullConfiguration basepullConfiguration)
{
this.basepullConfiguration = basepullConfiguration;
return (this);
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.metadata.tables;
import java.io.Serializable;
/*******************************************************************************
**
*******************************************************************************/
public class AssociatedScript implements Serializable
{
private String fieldName;
private Serializable scriptTypeId;
/*******************************************************************************
** Getter for fieldName
**
*******************************************************************************/
public String getFieldName()
{
return fieldName;
}
/*******************************************************************************
** Setter for fieldName
**
*******************************************************************************/
public void setFieldName(String fieldName)
{
this.fieldName = fieldName;
}
/*******************************************************************************
** Fluent setter for fieldName
**
*******************************************************************************/
public AssociatedScript withFieldName(String fieldName)
{
this.fieldName = fieldName;
return (this);
}
/*******************************************************************************
** Getter for scriptTypeId
**
*******************************************************************************/
public Serializable getScriptTypeId()
{
return scriptTypeId;
}
/*******************************************************************************
** Setter for scriptTypeId
**
*******************************************************************************/
public void setScriptTypeId(Serializable scriptTypeId)
{
this.scriptTypeId = scriptTypeId;
}
/*******************************************************************************
** Fluent setter for scriptTypeId
**
*******************************************************************************/
public AssociatedScript withScriptTypeId(Serializable scriptTypeId)
{
this.scriptTypeId = scriptTypeId;
return (this);
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.metadata.tables;
/*******************************************************************************
** Things that can be done to tables, fields.
**
*******************************************************************************/
public enum Capability
{
TABLE_QUERY,
TABLE_GET,
TABLE_COUNT,
TABLE_INSERT,
TABLE_UPDATE,
TABLE_DELETE
//////////////////////////////////////////////////////////////////////////
// keep these values in sync with AdornmentType.ts in qqq-frontend-core //
//////////////////////////////////////////////////////////////////////////
}

View File

@ -38,6 +38,8 @@ public class QFieldSection
private List<String> fieldNames;
private QIcon icon;
private boolean isHidden = false;
/*******************************************************************************
@ -244,4 +246,38 @@ public class QFieldSection
return (this);
}
/*******************************************************************************
** Getter for isHidden
**
*******************************************************************************/
public boolean getIsHidden()
{
return (isHidden);
}
/*******************************************************************************
** Setter for isHidden
**
*******************************************************************************/
public void setIsHidden(boolean isHidden)
{
this.isHidden = isHidden;
}
/*******************************************************************************
** Fluent Setter for isHidden
**
*******************************************************************************/
public QFieldSection withIsHidden(boolean isHidden)
{
this.isHidden = isHidden;
return (this);
}
}

View File

@ -26,10 +26,12 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizer;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
@ -78,7 +80,11 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
private List<QFieldSection> sections;
private List<String> widgets;
private List<String> widgets;
private List<AssociatedScript> associatedScripts;
private Set<Capability> enabledCapabilities = new HashSet<>();
private Set<Capability> disabledCapabilities = new HashSet<>();
@ -754,6 +760,56 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
/*******************************************************************************
** Getter for associatedScripts
**
*******************************************************************************/
public List<AssociatedScript> getAssociatedScripts()
{
return associatedScripts;
}
/*******************************************************************************
** Setter for associatedScripts
**
*******************************************************************************/
public void setAssociatedScripts(List<AssociatedScript> associatedScripts)
{
this.associatedScripts = associatedScripts;
}
/*******************************************************************************
** Fluent setter for associatedScripts
**
*******************************************************************************/
public QTableMetaData withAssociatedScripts(List<AssociatedScript> associatedScripts)
{
this.associatedScripts = associatedScripts;
return (this);
}
/*******************************************************************************
** Fluent setter for associatedScripts
**
*******************************************************************************/
public QTableMetaData withAssociatedScript(AssociatedScript associatedScript)
{
if(this.associatedScripts == null)
{
this.associatedScripts = new ArrayList();
}
this.associatedScripts.add(associatedScript);
return (this);
}
/*******************************************************************************
** Getter for uniqueKeys
**
@ -802,4 +858,181 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
return (this);
}
/*******************************************************************************
** Fluently add a section and fields in that section.
*******************************************************************************/
public QTableMetaData withSectionOfFields(QFieldSection fieldSection, QFieldMetaData... fields)
{
withSection(fieldSection);
List<String> fieldNames = new ArrayList<>();
for(QFieldMetaData field : fields)
{
withField(field);
fieldNames.add(field.getName());
}
fieldSection.setFieldNames(fieldNames);
return (this);
}
/*******************************************************************************
** Getter for enabledCapabilities
**
*******************************************************************************/
public Set<Capability> getEnabledCapabilities()
{
return enabledCapabilities;
}
/*******************************************************************************
** Setter for enabledCapabilities
**
*******************************************************************************/
public void setEnabledCapabilities(Set<Capability> enabledCapabilities)
{
this.enabledCapabilities = enabledCapabilities;
}
/*******************************************************************************
** Fluent setter for enabledCapabilities
**
*******************************************************************************/
public QTableMetaData withEnabledCapabilities(Set<Capability> enabledCapabilities)
{
this.enabledCapabilities = enabledCapabilities;
return (this);
}
/*******************************************************************************
** Alternative fluent setter for enabledCapabilities
**
*******************************************************************************/
public QTableMetaData withCapabilities(Set<Capability> enabledCapabilities)
{
this.enabledCapabilities = enabledCapabilities;
return (this);
}
/*******************************************************************************
** Alternative fluent setter for a single enabledCapabilities
**
*******************************************************************************/
public QTableMetaData withCapability(Capability capability)
{
if(this.enabledCapabilities == null)
{
this.enabledCapabilities = new HashSet<>();
}
this.enabledCapabilities.add(capability);
return (this);
}
/*******************************************************************************
** Fluent setter for enabledCapabilities
**
*******************************************************************************/
public QTableMetaData withCapabilities(Capability... enabledCapabilities)
{
if(this.enabledCapabilities == null)
{
this.enabledCapabilities = new HashSet<>();
}
this.enabledCapabilities.addAll(Arrays.stream(enabledCapabilities).toList());
return (this);
}
/*******************************************************************************
** Getter for disabledCapabilities
**
*******************************************************************************/
public Set<Capability> getDisabledCapabilities()
{
return disabledCapabilities;
}
/*******************************************************************************
** Setter for disabledCapabilities
**
*******************************************************************************/
public void setDisabledCapabilities(Set<Capability> disabledCapabilities)
{
this.disabledCapabilities = disabledCapabilities;
}
/*******************************************************************************
** Fluent setter for disabledCapabilities
**
*******************************************************************************/
public QTableMetaData withDisabledCapabilities(Set<Capability> disabledCapabilities)
{
this.disabledCapabilities = disabledCapabilities;
return (this);
}
/*******************************************************************************
** Fluent setter for disabledCapabilities
**
*******************************************************************************/
public QTableMetaData withoutCapabilities(Capability... disabledCapabilities)
{
if(this.disabledCapabilities == null)
{
this.disabledCapabilities = new HashSet<>();
}
this.disabledCapabilities.addAll(Arrays.stream(disabledCapabilities).toList());
return (this);
}
/*******************************************************************************
** Alternative fluent setter for disabledCapabilities
**
*******************************************************************************/
public QTableMetaData withoutCapabilities(Set<Capability> disabledCapabilities)
{
this.disabledCapabilities = disabledCapabilities;
return (this);
}
/*******************************************************************************
** Alternative fluent setter for a single disabledCapabilities
**
*******************************************************************************/
public QTableMetaData withoutCapability(Capability capability)
{
if(this.disabledCapabilities == null)
{
this.disabledCapabilities = new HashSet<>();
}
this.disabledCapabilities.add(capability);
return (this);
}
}

View File

@ -0,0 +1,282 @@
/*
* 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.scripts;
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;
/*******************************************************************************
**
*******************************************************************************/
public class Script extends QRecordEntity
{
public static final String TABLE_NAME = "script";
@QField()
private Integer id;
@QField()
private Instant createDate;
@QField()
private Instant modifyDate;
@QField()
private String name;
@QField(possibleValueSourceName = "scriptType")
private Integer scriptTypeId;
@QField(possibleValueSourceName = "scriptRevision")
private Integer currentScriptRevisionId;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public Script()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public Script(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;
}
/*******************************************************************************
** Fluent setter for id
**
*******************************************************************************/
public Script withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for createDate
**
*******************************************************************************/
public Instant getCreateDate()
{
return createDate;
}
/*******************************************************************************
** Setter for createDate
**
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Fluent setter for createDate
**
*******************************************************************************/
public Script withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Getter for modifyDate
**
*******************************************************************************/
public Instant getModifyDate()
{
return modifyDate;
}
/*******************************************************************************
** Setter for modifyDate
**
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Fluent setter for modifyDate
**
*******************************************************************************/
public Script withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Setter for name
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Fluent setter for name
**
*******************************************************************************/
public Script withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Getter for scriptTypeId
**
*******************************************************************************/
public Integer getScriptTypeId()
{
return scriptTypeId;
}
/*******************************************************************************
** Setter for scriptTypeId
**
*******************************************************************************/
public void setScriptTypeId(Integer scriptTypeId)
{
this.scriptTypeId = scriptTypeId;
}
/*******************************************************************************
** Fluent setter for scriptTypeId
**
*******************************************************************************/
public Script withScriptTypeId(Integer scriptTypeId)
{
this.scriptTypeId = scriptTypeId;
return (this);
}
/*******************************************************************************
** Getter for currentScriptRevisionId
**
*******************************************************************************/
public Integer getCurrentScriptRevisionId()
{
return currentScriptRevisionId;
}
/*******************************************************************************
** Setter for currentScriptRevisionId
**
*******************************************************************************/
public void setCurrentScriptRevisionId(Integer currentScriptRevisionId)
{
this.currentScriptRevisionId = currentScriptRevisionId;
}
/*******************************************************************************
** Fluent setter for currentScriptRevisionId
**
*******************************************************************************/
public Script withCurrentScriptRevisionId(Integer currentScriptRevisionId)
{
this.currentScriptRevisionId = currentScriptRevisionId;
return (this);
}
}

View File

@ -0,0 +1,482 @@
/*
* 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.scripts;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
/*******************************************************************************
**
*******************************************************************************/
public class ScriptLog extends QRecordEntity
{
public static final String TABLE_NAME = "scriptLog";
@QField()
private Integer id;
@QField()
private Instant createDate;
@QField()
private Instant modifyDate;
@QField(possibleValueSourceName = "script")
private Integer scriptId;
@QField(possibleValueSourceName = "scriptRevision")
private Integer scriptRevisionId;
@QField()
private Instant startTimestamp;
@QField()
private Instant endTimestamp;
@QField(displayFormat = DisplayFormat.COMMAS)
private Integer runTimeMillis;
@QField()
private Boolean hadError;
@QField()
private String input;
@QField()
private String output;
@QField()
private String error;
/*******************************************************************************
** Getter for id
**
*******************************************************************************/
public Integer getId()
{
return id;
}
/*******************************************************************************
** Setter for id
**
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id
**
*******************************************************************************/
public ScriptLog withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for createDate
**
*******************************************************************************/
public Instant getCreateDate()
{
return createDate;
}
/*******************************************************************************
** Setter for createDate
**
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Fluent setter for createDate
**
*******************************************************************************/
public ScriptLog withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Getter for modifyDate
**
*******************************************************************************/
public Instant getModifyDate()
{
return modifyDate;
}
/*******************************************************************************
** Setter for modifyDate
**
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Fluent setter for modifyDate
**
*******************************************************************************/
public ScriptLog withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Getter for scriptId
**
*******************************************************************************/
public Integer getScriptId()
{
return scriptId;
}
/*******************************************************************************
** Setter for scriptId
**
*******************************************************************************/
public void setScriptId(Integer scriptId)
{
this.scriptId = scriptId;
}
/*******************************************************************************
** Fluent setter for scriptId
**
*******************************************************************************/
public ScriptLog withScriptId(Integer scriptId)
{
this.scriptId = scriptId;
return (this);
}
/*******************************************************************************
** Getter for scriptRevisionId
**
*******************************************************************************/
public Integer getScriptRevisionId()
{
return scriptRevisionId;
}
/*******************************************************************************
** Setter for scriptRevisionId
**
*******************************************************************************/
public void setScriptRevisionId(Integer scriptRevisionId)
{
this.scriptRevisionId = scriptRevisionId;
}
/*******************************************************************************
** Fluent setter for scriptRevisionId
**
*******************************************************************************/
public ScriptLog withScriptRevisionId(Integer scriptRevisionId)
{
this.scriptRevisionId = scriptRevisionId;
return (this);
}
/*******************************************************************************
** Getter for startTimestamp
**
*******************************************************************************/
public Instant getStartTimestamp()
{
return startTimestamp;
}
/*******************************************************************************
** Setter for startTimestamp
**
*******************************************************************************/
public void setStartTimestamp(Instant startTimestamp)
{
this.startTimestamp = startTimestamp;
}
/*******************************************************************************
** Fluent setter for startTimestamp
**
*******************************************************************************/
public ScriptLog withStartTimestamp(Instant startTimestamp)
{
this.startTimestamp = startTimestamp;
return (this);
}
/*******************************************************************************
** Getter for endTimestamp
**
*******************************************************************************/
public Instant getEndTimestamp()
{
return endTimestamp;
}
/*******************************************************************************
** Setter for endTimestamp
**
*******************************************************************************/
public void setEndTimestamp(Instant endTimestamp)
{
this.endTimestamp = endTimestamp;
}
/*******************************************************************************
** Fluent setter for endTimestamp
**
*******************************************************************************/
public ScriptLog withEndTimestamp(Instant endTimestamp)
{
this.endTimestamp = endTimestamp;
return (this);
}
/*******************************************************************************
** Getter for runTimeMillis
**
*******************************************************************************/
public Integer getRunTimeMillis()
{
return runTimeMillis;
}
/*******************************************************************************
** Setter for runTimeMillis
**
*******************************************************************************/
public void setRunTimeMillis(Integer runTimeMillis)
{
this.runTimeMillis = runTimeMillis;
}
/*******************************************************************************
** Fluent setter for runTimeMillis
**
*******************************************************************************/
public ScriptLog withRunTimeMillis(Integer runTimeMillis)
{
this.runTimeMillis = runTimeMillis;
return (this);
}
/*******************************************************************************
** Getter for hadError
**
*******************************************************************************/
public Boolean getHadError()
{
return hadError;
}
/*******************************************************************************
** Setter for hadError
**
*******************************************************************************/
public void setHadError(Boolean hadError)
{
this.hadError = hadError;
}
/*******************************************************************************
** Fluent setter for hadError
**
*******************************************************************************/
public ScriptLog withHadError(Boolean hadError)
{
this.hadError = hadError;
return (this);
}
/*******************************************************************************
** Getter for input
**
*******************************************************************************/
public String getInput()
{
return input;
}
/*******************************************************************************
** Setter for input
**
*******************************************************************************/
public void setInput(String input)
{
this.input = input;
}
/*******************************************************************************
** Fluent setter for input
**
*******************************************************************************/
public ScriptLog withInput(String input)
{
this.input = input;
return (this);
}
/*******************************************************************************
** Getter for output
**
*******************************************************************************/
public String getOutput()
{
return output;
}
/*******************************************************************************
** Setter for output
**
*******************************************************************************/
public void setOutput(String output)
{
this.output = output;
}
/*******************************************************************************
** Fluent setter for output
**
*******************************************************************************/
public ScriptLog withOutput(String output)
{
this.output = output;
return (this);
}
/*******************************************************************************
** Getter for error
**
*******************************************************************************/
public String getError()
{
return error;
}
/*******************************************************************************
** Setter for error
**
*******************************************************************************/
public void setError(String error)
{
this.error = error;
}
/*******************************************************************************
** Fluent setter for error
**
*******************************************************************************/
public ScriptLog withError(String error)
{
this.error = error;
return (this);
}
}

View File

@ -0,0 +1,259 @@
/*
* 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.scripts;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
**
*******************************************************************************/
public class ScriptLogLine extends QRecordEntity
{
public static final String TABLE_NAME = "scriptLogLine";
@QField()
private Integer id;
@QField()
private Instant createDate;
@QField()
private Instant modifyDate;
@QField(possibleValueSourceName = "scriptLog")
private Integer scriptLogId;
@QField()
private Instant timestamp;
@QField()
private String text;
/*******************************************************************************
** Getter for id
**
*******************************************************************************/
public Integer getId()
{
return id;
}
/*******************************************************************************
** Setter for id
**
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id
**
*******************************************************************************/
public ScriptLogLine withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for createDate
**
*******************************************************************************/
public Instant getCreateDate()
{
return createDate;
}
/*******************************************************************************
** Setter for createDate
**
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Fluent setter for createDate
**
*******************************************************************************/
public ScriptLogLine withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Getter for modifyDate
**
*******************************************************************************/
public Instant getModifyDate()
{
return modifyDate;
}
/*******************************************************************************
** Setter for modifyDate
**
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Fluent setter for modifyDate
**
*******************************************************************************/
public ScriptLogLine withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Getter for scriptLogId
**
*******************************************************************************/
public Integer getScriptLogId()
{
return scriptLogId;
}
/*******************************************************************************
** Setter for scriptLogId
**
*******************************************************************************/
public void setScriptLogId(Integer scriptLogId)
{
this.scriptLogId = scriptLogId;
}
/*******************************************************************************
** Fluent setter for scriptLogId
**
*******************************************************************************/
public ScriptLogLine withScriptLogId(Integer scriptLogId)
{
this.scriptLogId = scriptLogId;
return (this);
}
/*******************************************************************************
** Getter for timestamp
**
*******************************************************************************/
public Instant getTimestamp()
{
return timestamp;
}
/*******************************************************************************
** Setter for timestamp
**
*******************************************************************************/
public void setTimestamp(Instant timestamp)
{
this.timestamp = timestamp;
}
/*******************************************************************************
** Fluent setter for timestamp
**
*******************************************************************************/
public ScriptLogLine withTimestamp(Instant timestamp)
{
this.timestamp = timestamp;
return (this);
}
/*******************************************************************************
** Getter for text
**
*******************************************************************************/
public String getText()
{
return text;
}
/*******************************************************************************
** Setter for text
**
*******************************************************************************/
public void setText(String text)
{
this.text = text;
}
/*******************************************************************************
** Fluent setter for text
**
*******************************************************************************/
public ScriptLogLine withText(String text)
{
this.text = text;
return (this);
}
}

View File

@ -0,0 +1,356 @@
/*
* 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.scripts;
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;
/*******************************************************************************
**
*******************************************************************************/
public class ScriptRevision extends QRecordEntity
{
public static final String TABLE_NAME = "scriptRevision";
@QField()
private Integer id;
@QField()
private Instant createDate;
@QField()
private Instant modifyDate;
@QField(possibleValueSourceName = "script")
private Integer scriptId;
@QField()
private String contents;
@QField()
private Integer sequenceNo;
@QField()
private String commitMessage;
@QField()
private String author;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ScriptRevision()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ScriptRevision(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;
}
/*******************************************************************************
** Fluent setter for id
**
*******************************************************************************/
public ScriptRevision withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for createDate
**
*******************************************************************************/
public Instant getCreateDate()
{
return createDate;
}
/*******************************************************************************
** Setter for createDate
**
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Fluent setter for createDate
**
*******************************************************************************/
public ScriptRevision withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Getter for modifyDate
**
*******************************************************************************/
public Instant getModifyDate()
{
return modifyDate;
}
/*******************************************************************************
** Setter for modifyDate
**
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Fluent setter for modifyDate
**
*******************************************************************************/
public ScriptRevision withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Getter for scriptId
**
*******************************************************************************/
public Integer getScriptId()
{
return scriptId;
}
/*******************************************************************************
** Setter for scriptId
**
*******************************************************************************/
public void setScriptId(Integer scriptId)
{
this.scriptId = scriptId;
}
/*******************************************************************************
** Fluent setter for scriptId
**
*******************************************************************************/
public ScriptRevision withScriptId(Integer scriptId)
{
this.scriptId = scriptId;
return (this);
}
/*******************************************************************************
** Getter for contents
**
*******************************************************************************/
public String getContents()
{
return contents;
}
/*******************************************************************************
** Setter for contents
**
*******************************************************************************/
public void setContents(String contents)
{
this.contents = contents;
}
/*******************************************************************************
** Fluent setter for contents
**
*******************************************************************************/
public ScriptRevision withContents(String contents)
{
this.contents = contents;
return (this);
}
/*******************************************************************************
** Getter for sequenceNo
**
*******************************************************************************/
public Integer getSequenceNo()
{
return sequenceNo;
}
/*******************************************************************************
** Setter for sequenceNo
**
*******************************************************************************/
public void setSequenceNo(Integer sequenceNo)
{
this.sequenceNo = sequenceNo;
}
/*******************************************************************************
** Fluent setter for sequenceNo
**
*******************************************************************************/
public ScriptRevision withSequenceNo(Integer sequenceNo)
{
this.sequenceNo = sequenceNo;
return (this);
}
/*******************************************************************************
** Getter for commitMessage
**
*******************************************************************************/
public String getCommitMessage()
{
return commitMessage;
}
/*******************************************************************************
** Setter for commitMessage
**
*******************************************************************************/
public void setCommitMessage(String commitMessage)
{
this.commitMessage = commitMessage;
}
/*******************************************************************************
** Fluent setter for commitMessage
**
*******************************************************************************/
public ScriptRevision withCommitMessage(String commitMessage)
{
this.commitMessage = commitMessage;
return (this);
}
/*******************************************************************************
** Getter for author
**
*******************************************************************************/
public String getAuthor()
{
return author;
}
/*******************************************************************************
** Setter for author
**
*******************************************************************************/
public void setAuthor(String author)
{
this.author = author;
}
/*******************************************************************************
** Fluent setter for author
**
*******************************************************************************/
public ScriptRevision withAuthor(String author)
{
this.author = author;
return (this);
}
}

View File

@ -0,0 +1,259 @@
/*
* 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.scripts;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
**
*******************************************************************************/
public class ScriptType extends QRecordEntity
{
public static final String TABLE_NAME = "scriptType";
@QField()
private Integer id;
@QField()
private Instant createDate;
@QField()
private Instant modifyDate;
@QField()
private String name;
@QField()
private String helpText;
@QField()
private String sampleCode;
/*******************************************************************************
** Getter for id
**
*******************************************************************************/
public Integer getId()
{
return id;
}
/*******************************************************************************
** Setter for id
**
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id
**
*******************************************************************************/
public ScriptType withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for createDate
**
*******************************************************************************/
public Instant getCreateDate()
{
return createDate;
}
/*******************************************************************************
** Setter for createDate
**
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Fluent setter for createDate
**
*******************************************************************************/
public ScriptType withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Getter for modifyDate
**
*******************************************************************************/
public Instant getModifyDate()
{
return modifyDate;
}
/*******************************************************************************
** Setter for modifyDate
**
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Fluent setter for modifyDate
**
*******************************************************************************/
public ScriptType withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Setter for name
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Fluent setter for name
**
*******************************************************************************/
public ScriptType withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Getter for helpText
**
*******************************************************************************/
public String getHelpText()
{
return helpText;
}
/*******************************************************************************
** Setter for helpText
**
*******************************************************************************/
public void setHelpText(String helpText)
{
this.helpText = helpText;
}
/*******************************************************************************
** Fluent setter for helpText
**
*******************************************************************************/
public ScriptType withHelpText(String helpText)
{
this.helpText = helpText;
return (this);
}
/*******************************************************************************
** Getter for sampleCode
**
*******************************************************************************/
public String getSampleCode()
{
return sampleCode;
}
/*******************************************************************************
** Setter for sampleCode
**
*******************************************************************************/
public void setSampleCode(String sampleCode)
{
this.sampleCode = sampleCode;
}
/*******************************************************************************
** Fluent setter for sampleCode
**
*******************************************************************************/
public ScriptType withSampleCode(String sampleCode)
{
this.sampleCode = sampleCode;
return (this);
}
}

View File

@ -0,0 +1,217 @@
/*
* 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.scripts;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
/*******************************************************************************
**
*******************************************************************************/
public class ScriptsMetaDataProvider
{
/*******************************************************************************
**
*******************************************************************************/
public void defineAll(QInstance instance, String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
{
defineStandardScriptsTables(instance, backendName, backendDetailEnricher);
defineStandardScriptsPossibleValueSources(instance);
}
/*******************************************************************************
**
*******************************************************************************/
public void defineStandardScriptsTables(QInstance instance, String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
{
for(QTableMetaData tableMetaData : defineStandardScriptsTables(backendName, backendDetailEnricher))
{
instance.addTable(tableMetaData);
}
}
/*******************************************************************************
**
*******************************************************************************/
public void defineStandardScriptsPossibleValueSources(QInstance instance) throws QException
{
instance.addPossibleValueSource(new QPossibleValueSource()
.withName(Script.TABLE_NAME)
.withTableName(Script.TABLE_NAME)
);
instance.addPossibleValueSource(new QPossibleValueSource()
.withName(ScriptRevision.TABLE_NAME)
.withTableName(ScriptRevision.TABLE_NAME)
);
instance.addPossibleValueSource(new QPossibleValueSource()
.withName(ScriptType.TABLE_NAME)
.withTableName(ScriptType.TABLE_NAME)
);
instance.addPossibleValueSource(new QPossibleValueSource()
.withName(ScriptLog.TABLE_NAME)
.withTableName(ScriptLog.TABLE_NAME)
);
}
/*******************************************************************************
**
*******************************************************************************/
private List<QTableMetaData> defineStandardScriptsTables(String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
{
List<QTableMetaData> rs = new ArrayList<>();
rs.add(enrich(backendDetailEnricher, defineScriptTypeTable(backendName)));
rs.add(enrich(backendDetailEnricher, defineScriptTable(backendName)));
rs.add(enrich(backendDetailEnricher, defineScriptRevisionTable(backendName)));
rs.add(enrich(backendDetailEnricher, defineScriptLogTable(backendName)));
rs.add(enrich(backendDetailEnricher, defineScriptLogLineTable(backendName)));
return (rs);
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData enrich(Consumer<QTableMetaData> backendDetailEnricher, QTableMetaData table)
{
if(backendDetailEnricher != null)
{
backendDetailEnricher.accept(table);
}
return (table);
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData defineStandardTable(String backendName, String name, Class<? extends QRecordEntity> fieldsFromEntity) throws QException
{
return new QTableMetaData()
.withName(name)
.withBackendName(backendName)
.withRecordLabelFormat("%s")
.withRecordLabelFields("name")
.withPrimaryKeyField("id")
.withFieldsFromEntity(fieldsFromEntity);
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData defineScriptTable(String backendName) throws QException
{
return (defineStandardTable(backendName, Script.TABLE_NAME, Script.class)
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "name", "scriptTypeId", "currentScriptRevisionId")))
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate"))));
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData defineScriptTypeTable(String backendName) throws QException
{
QTableMetaData tableMetaData = defineStandardTable(backendName, ScriptType.TABLE_NAME, ScriptType.class)
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "name")))
.withSection(new QFieldSection("details", new QIcon().withName("dataset"), Tier.T2, List.of("helpText", "sampleCode")))
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
tableMetaData.getField("sampleCode").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR));
return (tableMetaData);
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData defineScriptRevisionTable(String backendName) throws QException
{
QTableMetaData tableMetaData = defineStandardTable(backendName, ScriptRevision.TABLE_NAME, ScriptRevision.class)
.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE)
.withRecordLabelFormat("%s v%s")
.withRecordLabelFields(List.of("scriptId", "sequenceNo"))
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "scriptId", "sequenceNo")))
.withSection(new QFieldSection("code", new QIcon().withName("data_object"), Tier.T2, List.of("contents")))
.withSection(new QFieldSection("changeManagement", new QIcon().withName("history"), Tier.T2, List.of("commitMessage", "author")))
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
tableMetaData.getField("contents").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR));
return (tableMetaData);
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData defineScriptLogTable(String backendName) throws QException
{
return (defineStandardTable(backendName, ScriptLog.TABLE_NAME, ScriptLog.class)
.withRecordLabelFields(List.of("id"))
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id")))
.withSection(new QFieldSection("script", new QIcon().withName("data_object"), Tier.T2, List.of("scriptId", "scriptRevisionId")))
.withSection(new QFieldSection("timing", new QIcon().withName("schedule"), Tier.T2, List.of("startTimestamp", "endTimestamp", "runTimeMillis", "createDate", "modifyDate")))
.withSection(new QFieldSection("error", "Error", new QIcon().withName("error_outline"), Tier.T2, List.of("hadError", "error")))
.withSection(new QFieldSection("inputOutput", "Input/Output", new QIcon().withName("chat"), Tier.T2, List.of("input", "output"))));
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData defineScriptLogLineTable(String backendName) throws QException
{
return (defineStandardTable(backendName, ScriptLogLine.TABLE_NAME, ScriptLogLine.class)
.withRecordLabelFields(List.of("id")));
}
}

View File

@ -22,12 +22,16 @@
package com.kingsrook.qqq.backend.core.modules.authentication;
import java.nio.charset.StandardCharsets;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Optional;
import com.auth0.client.auth.AuthAPI;
import com.auth0.exception.Auth0Exception;
import com.auth0.json.auth.TokenHolder;
import com.auth0.jwk.Jwk;
import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider;
@ -65,6 +69,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
public static final int ID_TOKEN_VALIDATION_INTERVAL_SECONDS = 1800;
public static final String AUTH0_ID_TOKEN_KEY = "sessionId";
public static final String BASIC_AUTH_KEY = "basicAuthString";
public static final String TOKEN_NOT_PROVIDED_ERROR = "Id Token was not provided";
public static final String COULD_NOT_DECODE_ERROR = "Unable to decode id token";
@ -82,6 +87,43 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
@Override
public QSession createSession(QInstance qInstance, Map<String, String> context) throws QAuthenticationException
{
///////////////////////////////////////////////////////////
// check if we are processing a Basic Auth Session first //
///////////////////////////////////////////////////////////
if(context.containsKey(BASIC_AUTH_KEY))
{
Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication();
AuthAPI auth = new AuthAPI(metaData.getBaseUrl(), metaData.getClientId(), metaData.getClientSecret());
try
{
/////////////////////////////////////////////////
// decode the credentials from the header auth //
/////////////////////////////////////////////////
String base64Credentials = context.get(BASIC_AUTH_KEY).trim();
byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
String credentials = new String(credDecoded, StandardCharsets.UTF_8);
/////////////////////////////////////
// call auth0 with a login request //
/////////////////////////////////////
TokenHolder result = auth.login(credentials.split(":")[0], credentials.split(":")[1].toCharArray())
.setScope("openid email nickname")
.execute();
context.put(AUTH0_ID_TOKEN_KEY, result.getIdToken());
}
catch(Auth0Exception e)
{
////////////////
// ¯\_(ツ)_/¯ //
////////////////
String message = "An unknown error occurred during handling basic auth";
LOG.error(message, e);
throw (new QAuthenticationException(message));
}
}
//////////////////////////////////////////////////
// get the jwt id token from the context object //
//////////////////////////////////////////////////

View File

@ -31,6 +31,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
public class Auth0AuthenticationMetaData extends QAuthenticationMetaData
{
private String baseUrl;
private String clientId;
private String clientSecret;
@ -76,4 +78,69 @@ public class Auth0AuthenticationMetaData extends QAuthenticationMetaData
this.baseUrl = baseUrl;
}
/*******************************************************************************
** Fluent setter, override to help fluent flows
*******************************************************************************/
public Auth0AuthenticationMetaData withClientId(String clientId)
{
setClientId(clientId);
return this;
}
/*******************************************************************************
** Getter for clientId
**
*******************************************************************************/
public String getClientId()
{
return clientId;
}
/*******************************************************************************
** Setter for clientId
**
*******************************************************************************/
public void setClientId(String clientId)
{
this.clientId = clientId;
}
/*******************************************************************************
** Fluent setter, override to help fluent flows
*******************************************************************************/
public Auth0AuthenticationMetaData withClientSecret(String clientSecret)
{
setClientSecret(clientSecret);
return this;
}
/*******************************************************************************
** Getter for clientSecret
**
*******************************************************************************/
public String getClientSecret()
{
return clientSecret;
}
/*******************************************************************************
** Setter for clientSecret
**
*******************************************************************************/
public void setClientSecret(String clientSecret)
{
this.clientSecret = clientSecret;
}
}

View File

@ -73,6 +73,7 @@ public class QBackendModuleDispatcher
// e.g., backend-core shouldn't need to "know" about the modules.
"com.kingsrook.qqq.backend.core.modules.backend.implementations.mock.MockBackendModule",
"com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule",
"com.kingsrook.qqq.backend.core.modules.backend.implementations.enumeration.EnumerationBackendModule",
"com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule",
"com.kingsrook.qqq.backend.module.filesystem.local.FilesystemBackendModule",
"com.kingsrook.qqq.backend.module.filesystem.s3.S3BackendModule",

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.modules.backend;
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
import com.kingsrook.qqq.backend.core.actions.interfaces.GetInterface;
import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface;
@ -48,7 +49,10 @@ public interface QBackendModuleInterface
/*******************************************************************************
** Method to identify the class used for backend meta data for this module.
*******************************************************************************/
Class<? extends QBackendMetaData> getBackendMetaDataClass();
default Class<? extends QBackendMetaData> getBackendMetaDataClass()
{
return (QBackendMetaData.class);
}
/*******************************************************************************
** Method to identify the class used for table-backend details for this module.
@ -76,6 +80,15 @@ public interface QBackendModuleInterface
return null;
}
/*******************************************************************************
**
*******************************************************************************/
default GetInterface getGetInterface()
{
throwNotImplemented("Get");
return null;
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -0,0 +1,71 @@
/*
* 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.modules.backend.implementations.enumeration;
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
/*******************************************************************************
** Backend module for a table based on a java enum. So we can expose an enum
** as a table (similar to exposing an enum as a possible value source), with multiple
** fields in the enum (exposed via getter methods in the enum) as fields in the table.
**
** Only supports read-operations, as you can't modify an enum.
*******************************************************************************/
public class EnumerationBackendModule implements QBackendModuleInterface
{
/*******************************************************************************
** Method where a backend module must be able to provide its type (name).
*******************************************************************************/
@Override
public String getBackendType()
{
return ("enum");
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Class<? extends QTableBackendDetails> getTableBackendDetailsClass()
{
return (EnumerationTableBackendDetails.class);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public QueryInterface getQueryInterface()
{
return new EnumerationQueryAction();
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.modules.backend.implementations.enumeration;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEnum;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
/*******************************************************************************
**
*******************************************************************************/
public class EnumerationQueryAction implements QueryInterface
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public QueryOutput execute(QueryInput queryInput) throws QException
{
try
{
QTableMetaData table = queryInput.getTable();
EnumerationTableBackendDetails backendDetails = (EnumerationTableBackendDetails) table.getBackendDetails();
Class<? extends QRecordEnum> enumClass = backendDetails.getEnumClass();
QRecordEnum[] values = (QRecordEnum[]) enumClass.getMethod("values").invoke(null);
//////////////////////////////////////////////
// note - not good streaming behavior here. //
//////////////////////////////////////////////
List<QRecord> recordList = new ArrayList<>();
for(QRecordEnum value : values)
{
QRecord record = value.toQRecord();
boolean recordMatches = BackendQueryFilterUtils.doesRecordMatch(queryInput.getFilter(), record);
if(recordMatches)
{
recordList.add(record);
}
}
BackendQueryFilterUtils.sortRecordList(queryInput.getFilter(), recordList);
recordList = BackendQueryFilterUtils.applySkipAndLimit(queryInput, recordList);
QueryOutput queryOutput = new QueryOutput(queryInput);
queryOutput.addRecords(recordList);
return queryOutput;
}
catch(Exception e)
{
throw (new QException("Error executing query", e));
}
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.modules.backend.implementations.enumeration;
import com.kingsrook.qqq.backend.core.model.data.QRecordEnum;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
/*******************************************************************************
**
*******************************************************************************/
public class EnumerationTableBackendDetails extends QTableBackendDetails
{
private Class<? extends QRecordEnum> enumClass;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public EnumerationTableBackendDetails()
{
super();
setBackendType(EnumerationBackendModule.class);
}
/*******************************************************************************
** Getter for enumClass
**
*******************************************************************************/
public Class<? extends QRecordEnum> getEnumClass()
{
return enumClass;
}
/*******************************************************************************
** Setter for enumClass
**
*******************************************************************************/
public void setEnumClass(Class<? extends QRecordEnum> enumClass)
{
this.enumClass = enumClass;
}
/*******************************************************************************
** Fluent setter for enumClass
**
*******************************************************************************/
public EnumerationTableBackendDetails withEnumClass(Class<? extends QRecordEnum> enumClass)
{
this.enumClass = enumClass;
return (this);
}
}

View File

@ -27,7 +27,6 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
@ -53,17 +52,6 @@ public class MemoryBackendModule implements QBackendModuleInterface
/*******************************************************************************
** Method to identify the class used for backend meta data for this module.
*******************************************************************************/
@Override
public Class<? extends QBackendMetaData> getBackendMetaDataClass()
{
return (QBackendMetaData.class);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -23,27 +23,21 @@ package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.commons.lang.NotImplementedException;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
/*******************************************************************************
@ -126,7 +120,7 @@ public class MemoryRecordStore
for(QRecord qRecord : tableData.values())
{
boolean recordMatches = doesRecordMatch(input.getFilter(), qRecord);
boolean recordMatches = BackendQueryFilterUtils.doesRecordMatch(input.getFilter(), qRecord);
if(recordMatches)
{
@ -139,349 +133,6 @@ public class MemoryRecordStore
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("checkstyle:indentation")
private boolean doesRecordMatch(QQueryFilter filter, QRecord qRecord)
{
if(filter == null || !filter.hasAnyCriteria())
{
return (true);
}
/////////////////////////////////////////////////////////////////////////////////////
// for an AND query, default to a TRUE answer, and we'll &= each criteria's value. //
// for an OR query, default to FALSE, and |= each criteria's value. //
/////////////////////////////////////////////////////////////////////////////////////
AtomicBoolean recordMatches = new AtomicBoolean(filter.getBooleanOperator().equals(QQueryFilter.BooleanOperator.AND) ? true : false);
///////////////////////////////////////
// if there are criteria, apply them //
///////////////////////////////////////
for(QFilterCriteria criterion : CollectionUtils.nonNullList(filter.getCriteria()))
{
String fieldName = criterion.getFieldName();
Serializable value = qRecord.getValue(fieldName);
boolean criterionMatches = switch(criterion.getOperator())
{
case EQUALS -> testEquals(criterion, value);
case NOT_EQUALS -> !testEquals(criterion, value);
case IN -> testIn(criterion, value);
case NOT_IN -> !testIn(criterion, value);
case IS_BLANK -> testBlank(criterion, value);
case IS_NOT_BLANK -> !testBlank(criterion, value);
case CONTAINS -> testContains(criterion, fieldName, value);
case NOT_CONTAINS -> !testContains(criterion, fieldName, value);
case STARTS_WITH -> testStartsWith(criterion, fieldName, value);
case NOT_STARTS_WITH -> !testStartsWith(criterion, fieldName, value);
case ENDS_WITH -> testEndsWith(criterion, fieldName, value);
case NOT_ENDS_WITH -> !testEndsWith(criterion, fieldName, value);
case GREATER_THAN -> testGreaterThan(criterion, value);
case GREATER_THAN_OR_EQUALS -> testGreaterThan(criterion, value) || testEquals(criterion, value);
case LESS_THAN -> !testGreaterThan(criterion, value) && !testEquals(criterion, value);
case LESS_THAN_OR_EQUALS -> !testGreaterThan(criterion, value);
case BETWEEN ->
{
QFilterCriteria criteria0 = new QFilterCriteria().withValues(criterion.getValues());
QFilterCriteria criteria1 = new QFilterCriteria().withValues(new ArrayList<>(criterion.getValues()));
criteria1.getValues().remove(0);
yield (testGreaterThan(criteria0, value) || testEquals(criteria0, value)) && (!testGreaterThan(criteria1, value) || testEquals(criteria1, value));
}
case NOT_BETWEEN ->
{
QFilterCriteria criteria0 = new QFilterCriteria().withValues(criterion.getValues());
QFilterCriteria criteria1 = new QFilterCriteria().withValues(criterion.getValues());
criteria1.getValues().remove(0);
yield !(testGreaterThan(criteria0, value) || testEquals(criteria0, value)) && (!testGreaterThan(criteria1, value) || testEquals(criteria1, value));
}
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// add this new value to the existing recordMatches value - and if we can short circuit the remaining checks, do so. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Boolean shortCircuitValue = applyBooleanOperator(recordMatches, criterionMatches, filter.getBooleanOperator());
if(shortCircuitValue != null)
{
return (shortCircuitValue);
}
}
////////////////////////////////////////
// apply sub-filters if there are any //
////////////////////////////////////////
for(QQueryFilter subFilter : CollectionUtils.nonNullList(filter.getSubFilters()))
{
boolean subFilterMatches = doesRecordMatch(subFilter, qRecord);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// add this new value to the existing recordMatches value - and if we can short circuit the remaining checks, do so. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Boolean shortCircuitValue = applyBooleanOperator(recordMatches, subFilterMatches, filter.getBooleanOperator());
if(shortCircuitValue != null)
{
return (shortCircuitValue);
}
}
return (recordMatches.getPlain());
}
/*******************************************************************************
** Based on an incoming boolean value (accumulator), a new value, and a boolean
** operator, update the accumulator, and if we can then short-circuit remaining
** operations, return a true or false. Returning null means to keep going.
*******************************************************************************/
private Boolean applyBooleanOperator(AtomicBoolean accumulator, boolean newValue, QQueryFilter.BooleanOperator booleanOperator)
{
boolean accumulatorValue = accumulator.getPlain();
if(booleanOperator.equals(QQueryFilter.BooleanOperator.AND))
{
accumulatorValue &= newValue;
if(!accumulatorValue)
{
return (false);
}
}
else
{
accumulatorValue |= newValue;
if(accumulatorValue)
{
return (true);
}
}
accumulator.set(accumulatorValue);
return (null);
}
/*******************************************************************************
**
*******************************************************************************/
private boolean testBlank(QFilterCriteria criterion, Serializable value)
{
if(value == null)
{
return (true);
}
if("".equals(ValueUtils.getValueAsString(value)))
{
return (true);
}
return (false);
}
/*******************************************************************************
**
*******************************************************************************/
private boolean testGreaterThan(QFilterCriteria criterion, Serializable value)
{
Serializable criterionValue = criterion.getValues().get(0);
if(criterionValue == null)
{
throw (new IllegalArgumentException("Missing criterion value in query"));
}
if(value == null)
{
/////////////////////////////////////////////////////////////////////////////////////
// a database would say 'false' for if a null column is > a value, so do the same. //
/////////////////////////////////////////////////////////////////////////////////////
return (false);
}
if(value instanceof LocalDate valueDate && criterionValue instanceof LocalDate criterionValueDate)
{
return (valueDate.isAfter(criterionValueDate));
}
if(value instanceof Number valueNumber && criterionValue instanceof Number criterionValueNumber)
{
return (valueNumber.doubleValue() > criterionValueNumber.doubleValue());
}
if(value instanceof LocalDate || criterionValue instanceof LocalDate)
{
LocalDate valueDate;
if(value instanceof LocalDate ld)
{
valueDate = ld;
}
else
{
valueDate = ValueUtils.getValueAsLocalDate(value);
}
LocalDate criterionDate;
if(criterionValue instanceof LocalDate ld)
{
criterionDate = ld;
}
else
{
criterionDate = ValueUtils.getValueAsLocalDate(criterionValue);
}
return (valueDate.isAfter(criterionDate));
}
throw (new NotImplementedException("Greater/Less Than comparisons are not (yet?) implemented for the supplied types [" + value.getClass().getSimpleName() + "][" + criterionValue.getClass().getSimpleName() + "]"));
}
/*******************************************************************************
**
*******************************************************************************/
private boolean testIn(QFilterCriteria criterion, Serializable value)
{
if(!criterion.getValues().contains(value))
{
return (false);
}
return (true);
}
/*******************************************************************************
**
*******************************************************************************/
private boolean testEquals(QFilterCriteria criterion, Serializable value)
{
if(value == null)
{
return (false);
}
Serializable criteriaValue = criterion.getValues().get(0);
if(value instanceof String && criteriaValue instanceof Number)
{
criteriaValue = String.valueOf(criteriaValue);
}
else if(criteriaValue instanceof String && value instanceof Number)
{
value = String.valueOf(value);
}
if(!value.equals(criteriaValue))
{
return (false);
}
return (true);
}
/*******************************************************************************
**
*******************************************************************************/
private boolean testContains(QFilterCriteria criterion, String fieldName, Serializable value)
{
String stringValue = getStringFieldValue(value, fieldName, criterion);
String criterionValue = getFirstStringCriterionValue(criterion);
if(!stringValue.contains(criterionValue))
{
return (false);
}
return (true);
}
/*******************************************************************************
**
*******************************************************************************/
private boolean testStartsWith(QFilterCriteria criterion, String fieldName, Serializable value)
{
String stringValue = getStringFieldValue(value, fieldName, criterion);
String criterionValue = getFirstStringCriterionValue(criterion);
if(!stringValue.startsWith(criterionValue))
{
return (false);
}
return (true);
}
/*******************************************************************************
**
*******************************************************************************/
private boolean testEndsWith(QFilterCriteria criterion, String fieldName, Serializable value)
{
String stringValue = getStringFieldValue(value, fieldName, criterion);
String criterionValue = getFirstStringCriterionValue(criterion);
if(!stringValue.endsWith(criterionValue))
{
return (false);
}
return (true);
}
/*******************************************************************************
**
*******************************************************************************/
private String getFirstStringCriterionValue(QFilterCriteria criteria)
{
if(CollectionUtils.nullSafeIsEmpty(criteria.getValues()))
{
throw new IllegalArgumentException("Missing value for [" + criteria.getOperator() + "] criteria on field [" + criteria.getFieldName() + "]");
}
Serializable value = criteria.getValues().get(0);
if(value == null)
{
return "";
}
if(!(value instanceof String stringValue))
{
throw new ClassCastException("Value [" + value + "] for criteria [" + criteria.getFieldName() + "] is not a String, which is required for the [" + criteria.getOperator() + "] operator.");
}
return (stringValue);
}
/*******************************************************************************
**
*******************************************************************************/
private String getStringFieldValue(Serializable value, String fieldName, QFilterCriteria criterion)
{
if(value == null)
{
return "";
}
if(!(value instanceof String stringValue))
{
throw new ClassCastException("Value [" + value + "] in field [" + fieldName + "] is not a String, which is required for the [" + criterion.getOperator() + "] operator.");
}
return (stringValue);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -0,0 +1,475 @@
/*
* 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.modules.backend.implementations.utils;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
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.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.commons.lang.NotImplementedException;
/*******************************************************************************
** Utility class for backend modules that need to do filter operations.
**
** e.g., like an in-memory module, or one that's working with files - basically
** one that doesn't have filtering provided by the backend (like a database or API).
*******************************************************************************/
public class BackendQueryFilterUtils
{
/*******************************************************************************
** Test if record matches filter.
*******************************************************************************/
@SuppressWarnings("checkstyle:indentation")
public static boolean doesRecordMatch(QQueryFilter filter, QRecord qRecord)
{
if(filter == null || !filter.hasAnyCriteria())
{
return (true);
}
/////////////////////////////////////////////////////////////////////////////////////
// for an AND query, default to a TRUE answer, and we'll &= each criteria's value. //
// for an OR query, default to FALSE, and |= each criteria's value. //
/////////////////////////////////////////////////////////////////////////////////////
AtomicBoolean recordMatches = new AtomicBoolean(filter.getBooleanOperator().equals(QQueryFilter.BooleanOperator.AND) ? true : false);
///////////////////////////////////////
// if there are criteria, apply them //
///////////////////////////////////////
for(QFilterCriteria criterion : CollectionUtils.nonNullList(filter.getCriteria()))
{
String fieldName = criterion.getFieldName();
Serializable value = qRecord.getValue(fieldName);
boolean criterionMatches = switch(criterion.getOperator())
{
case EQUALS -> testEquals(criterion, value);
case NOT_EQUALS -> !testEquals(criterion, value);
case IN -> testIn(criterion, value);
case NOT_IN -> !testIn(criterion, value);
case IS_BLANK -> testBlank(criterion, value);
case IS_NOT_BLANK -> !testBlank(criterion, value);
case CONTAINS -> testContains(criterion, fieldName, value);
case NOT_CONTAINS -> !testContains(criterion, fieldName, value);
case STARTS_WITH -> testStartsWith(criterion, fieldName, value);
case NOT_STARTS_WITH -> !testStartsWith(criterion, fieldName, value);
case ENDS_WITH -> testEndsWith(criterion, fieldName, value);
case NOT_ENDS_WITH -> !testEndsWith(criterion, fieldName, value);
case GREATER_THAN -> testGreaterThan(criterion, value);
case GREATER_THAN_OR_EQUALS -> testGreaterThan(criterion, value) || testEquals(criterion, value);
case LESS_THAN -> !testGreaterThan(criterion, value) && !testEquals(criterion, value);
case LESS_THAN_OR_EQUALS -> !testGreaterThan(criterion, value);
case BETWEEN ->
{
QFilterCriteria criteria0 = new QFilterCriteria().withValues(criterion.getValues());
QFilterCriteria criteria1 = new QFilterCriteria().withValues(new ArrayList<>(criterion.getValues()));
criteria1.getValues().remove(0);
yield (testGreaterThan(criteria0, value) || testEquals(criteria0, value)) && (!testGreaterThan(criteria1, value) || testEquals(criteria1, value));
}
case NOT_BETWEEN ->
{
QFilterCriteria criteria0 = new QFilterCriteria().withValues(criterion.getValues());
QFilterCriteria criteria1 = new QFilterCriteria().withValues(criterion.getValues());
criteria1.getValues().remove(0);
yield !(testGreaterThan(criteria0, value) || testEquals(criteria0, value)) && (!testGreaterThan(criteria1, value) || testEquals(criteria1, value));
}
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// add this new value to the existing recordMatches value - and if we can short circuit the remaining checks, do so. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Boolean shortCircuitValue = applyBooleanOperator(recordMatches, criterionMatches, filter.getBooleanOperator());
if(shortCircuitValue != null)
{
return (shortCircuitValue);
}
}
////////////////////////////////////////
// apply sub-filters if there are any //
////////////////////////////////////////
for(QQueryFilter subFilter : CollectionUtils.nonNullList(filter.getSubFilters()))
{
boolean subFilterMatches = doesRecordMatch(subFilter, qRecord);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// add this new value to the existing recordMatches value - and if we can short circuit the remaining checks, do so. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Boolean shortCircuitValue = applyBooleanOperator(recordMatches, subFilterMatches, filter.getBooleanOperator());
if(shortCircuitValue != null)
{
return (shortCircuitValue);
}
}
return (recordMatches.getPlain());
}
/*******************************************************************************
** Based on an incoming boolean value (accumulator), a new value, and a boolean
** operator, update the accumulator, and if we can then short-circuit remaining
** operations, return a true or false. Returning null means to keep going.
*******************************************************************************/
private static Boolean applyBooleanOperator(AtomicBoolean accumulator, boolean newValue, QQueryFilter.BooleanOperator booleanOperator)
{
boolean accumulatorValue = accumulator.getPlain();
if(booleanOperator.equals(QQueryFilter.BooleanOperator.AND))
{
accumulatorValue &= newValue;
if(!accumulatorValue)
{
return (false);
}
}
else
{
accumulatorValue |= newValue;
if(accumulatorValue)
{
return (true);
}
}
accumulator.set(accumulatorValue);
return (null);
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean testBlank(QFilterCriteria criterion, Serializable value)
{
if(value == null)
{
return (true);
}
if("".equals(ValueUtils.getValueAsString(value)))
{
return (true);
}
return (false);
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean testGreaterThan(QFilterCriteria criterion, Serializable value)
{
Serializable criterionValue = criterion.getValues().get(0);
if(criterionValue == null)
{
throw (new IllegalArgumentException("Missing criterion value in query"));
}
if(value == null)
{
/////////////////////////////////////////////////////////////////////////////////////
// a database would say 'false' for if a null column is > a value, so do the same. //
/////////////////////////////////////////////////////////////////////////////////////
return (false);
}
return isGreaterThan(criterionValue, value);
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean isGreaterThan(Serializable a, Serializable b)
{
if(Objects.equals(a, b))
{
return false;
}
if(b instanceof LocalDate valueDate && a instanceof LocalDate criterionValueDate)
{
return (valueDate.isAfter(criterionValueDate));
}
if(b instanceof Number valueNumber && a instanceof Number criterionValueNumber)
{
return (valueNumber.doubleValue() > criterionValueNumber.doubleValue());
}
if(b instanceof String valueString && a instanceof String criterionValueString)
{
return (valueString.compareTo(criterionValueString) > 0);
}
if(b instanceof LocalDate || a instanceof LocalDate)
{
LocalDate valueDate;
if(b instanceof LocalDate ld)
{
valueDate = ld;
}
else
{
valueDate = ValueUtils.getValueAsLocalDate(b);
}
LocalDate criterionDate;
if(a instanceof LocalDate ld)
{
criterionDate = ld;
}
else
{
criterionDate = ValueUtils.getValueAsLocalDate(a);
}
return (valueDate.isAfter(criterionDate));
}
throw (new NotImplementedException("Greater/Less Than comparisons are not (yet?) implemented for the supplied types [" + b.getClass().getSimpleName() + "][" + a.getClass().getSimpleName() + "]"));
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean testIn(QFilterCriteria criterion, Serializable value)
{
if(!criterion.getValues().contains(value))
{
return (false);
}
return (true);
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean testEquals(QFilterCriteria criterion, Serializable value)
{
if(value == null)
{
return (false);
}
Serializable criteriaValue = criterion.getValues().get(0);
if(value instanceof String && criteriaValue instanceof Number)
{
criteriaValue = String.valueOf(criteriaValue);
}
else if(criteriaValue instanceof String && value instanceof Number)
{
value = String.valueOf(value);
}
if(!value.equals(criteriaValue))
{
return (false);
}
return (true);
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean testContains(QFilterCriteria criterion, String fieldName, Serializable value)
{
String stringValue = getStringFieldValue(value, fieldName, criterion);
String criterionValue = getFirstStringCriterionValue(criterion);
if(!stringValue.contains(criterionValue))
{
return (false);
}
return (true);
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean testStartsWith(QFilterCriteria criterion, String fieldName, Serializable value)
{
String stringValue = getStringFieldValue(value, fieldName, criterion);
String criterionValue = getFirstStringCriterionValue(criterion);
if(!stringValue.startsWith(criterionValue))
{
return (false);
}
return (true);
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean testEndsWith(QFilterCriteria criterion, String fieldName, Serializable value)
{
String stringValue = getStringFieldValue(value, fieldName, criterion);
String criterionValue = getFirstStringCriterionValue(criterion);
if(!stringValue.endsWith(criterionValue))
{
return (false);
}
return (true);
}
/*******************************************************************************
**
*******************************************************************************/
private static String getFirstStringCriterionValue(QFilterCriteria criteria)
{
if(CollectionUtils.nullSafeIsEmpty(criteria.getValues()))
{
throw new IllegalArgumentException("Missing value for [" + criteria.getOperator() + "] criteria on field [" + criteria.getFieldName() + "]");
}
Serializable value = criteria.getValues().get(0);
if(value == null)
{
return "";
}
if(!(value instanceof String stringValue))
{
throw new ClassCastException("Value [" + value + "] for criteria [" + criteria.getFieldName() + "] is not a String, which is required for the [" + criteria.getOperator() + "] operator.");
}
return (stringValue);
}
/*******************************************************************************
**
*******************************************************************************/
private static String getStringFieldValue(Serializable value, String fieldName, QFilterCriteria criterion)
{
if(value == null)
{
return "";
}
if(!(value instanceof String stringValue))
{
throw new ClassCastException("Value [" + value + "] in field [" + fieldName + "] is not a String, which is required for the [" + criterion.getOperator() + "] operator.");
}
return (stringValue);
}
/*******************************************************************************
** Sort list of records based on filter.
*******************************************************************************/
public static void sortRecordList(QQueryFilter filter, List<QRecord> recordList)
{
if(filter == null || CollectionUtils.nullSafeIsEmpty(filter.getOrderBys()))
{
return;
}
recordList.sort((a, b) ->
{
for(QFilterOrderBy orderBy : filter.getOrderBys())
{
Serializable valueA = a.getValue(orderBy.getFieldName());
Serializable valueB = b.getValue(orderBy.getFieldName());
if(Objects.equals(valueA, valueB))
{
continue;
}
else if(isGreaterThan(valueA, valueB) && orderBy.getIsAscending())
{
return (-1);
}
else
{
return (1);
}
}
return (0);
});
}
/*******************************************************************************
** Apply skip & limit attributes from queryInput to a list of records.
*******************************************************************************/
public static List<QRecord> applySkipAndLimit(QueryInput queryInput, List<QRecord> recordList)
{
Integer skip = queryInput.getSkip();
if(skip != null && skip > 0)
{
if(skip < recordList.size())
{
recordList = recordList.subList(skip, recordList.size());
}
else
{
recordList.clear();
}
}
Integer limit = queryInput.getLimit();
if(limit != null && limit >= 0 && limit < recordList.size())
{
recordList = recordList.subList(0, limit);
}
return recordList;
}
}

View File

@ -0,0 +1,247 @@
/*
* 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.processes.implementations.basepull;
import java.io.Serializable;
/*******************************************************************************
** Class for storing all basepull configuration data
**
*******************************************************************************/
public class BasepullConfiguration implements Serializable
{
private String tableName; // the table that stores the basepull timestamps
private String keyField; // the field in the basepull timestamps table that stores the key of the basepull (e.g., a process name)
private String keyValue; // the key applied to the keyField - optional - if not set, process.getName is used.
private String lastRunTimeFieldName; // the field in the basepull timestamps table that stores the last-run time for the job.
private Integer hoursBackForInitialTimestamp; // for the first-run use-case (where there is no row in the timestamps table), how many hours back in time to look.
private String timestampField; // the name of the field in the table being queried against the last-run timestamp.
/*******************************************************************************
** Getter for tableName
**
*******************************************************************************/
public String getTableName()
{
return tableName;
}
/*******************************************************************************
** Setter for tableName
**
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName
**
*******************************************************************************/
public BasepullConfiguration withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Getter for keyField
**
*******************************************************************************/
public String getKeyField()
{
return keyField;
}
/*******************************************************************************
** Setter for keyField
**
*******************************************************************************/
public void setKeyField(String keyField)
{
this.keyField = keyField;
}
/*******************************************************************************
** Fluent setter for keyField
**
*******************************************************************************/
public BasepullConfiguration withKeyField(String keyField)
{
this.keyField = keyField;
return (this);
}
/*******************************************************************************
** Getter for keyValue
**
*******************************************************************************/
public String getKeyValue()
{
return keyValue;
}
/*******************************************************************************
** Setter for keyValue
**
*******************************************************************************/
public void setKeyValue(String keyValue)
{
this.keyValue = keyValue;
}
/*******************************************************************************
** Fluent setter for keyValue
**
*******************************************************************************/
public BasepullConfiguration withKeyValue(String keyValue)
{
this.keyValue = keyValue;
return (this);
}
/*******************************************************************************
** Getter for lastRunTimeFieldName
**
*******************************************************************************/
public String getLastRunTimeFieldName()
{
return lastRunTimeFieldName;
}
/*******************************************************************************
** Setter for lastRunTimeFieldName
**
*******************************************************************************/
public void setLastRunTimeFieldName(String lastRunTimeFieldName)
{
this.lastRunTimeFieldName = lastRunTimeFieldName;
}
/*******************************************************************************
** Fluent setter for lastRunTimeFieldName
**
*******************************************************************************/
public BasepullConfiguration withLastRunTimeFieldName(String lastRunTimeFieldName)
{
this.lastRunTimeFieldName = lastRunTimeFieldName;
return (this);
}
/*******************************************************************************
** Getter for hoursBackForInitialTimestamp
**
*******************************************************************************/
public Integer getHoursBackForInitialTimestamp()
{
return hoursBackForInitialTimestamp;
}
/*******************************************************************************
** Setter for hoursBackForInitialTimestamp
**
*******************************************************************************/
public void setHoursBackForInitialTimestamp(Integer hoursBackForInitialTimestamp)
{
this.hoursBackForInitialTimestamp = hoursBackForInitialTimestamp;
}
/*******************************************************************************
** Fluent setter for hoursBackForInitialTimestamp
**
*******************************************************************************/
public BasepullConfiguration withHoursBackForInitialTimestamp(Integer hoursBackForInitialTimestamp)
{
this.hoursBackForInitialTimestamp = hoursBackForInitialTimestamp;
return (this);
}
/*******************************************************************************
** Getter for timestampField
**
*******************************************************************************/
public String getTimestampField()
{
return timestampField;
}
/*******************************************************************************
** Setter for timestampField
**
*******************************************************************************/
public void setTimestampField(String timestampField)
{
this.timestampField = timestampField;
}
/*******************************************************************************
** Fluent setter for timestampField
**
*******************************************************************************/
public BasepullConfiguration withTimestampField(String timestampField)
{
this.timestampField = timestampField;
return (this);
}
}

View File

@ -0,0 +1,102 @@
/*
* 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.processes.implementations.basepull;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
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.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
/*******************************************************************************
** Version of ExtractViaQueryStep that knows how to set up a basepull query.
*******************************************************************************/
public class ExtractViaBasepullQueryStep extends ExtractViaQueryStep
{
/*******************************************************************************
**
*******************************************************************************/
protected QQueryFilter getQueryFilter(RunBackendStepInput runBackendStepInput) throws QException
{
//////////////////////////////////////////////////////////////
// get input query filter or if not found, create a new one //
//////////////////////////////////////////////////////////////
QQueryFilter queryFilter = new QQueryFilter();
try
{
queryFilter = super.getQueryFilter(runBackendStepInput);
}
catch(QException qe)
{
///////////////////////////////////////////////////////////////////////////////////////
// if we catch here, assume that is because there was no default filter, continue on //
///////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// build up a query filter that is against the source table for the given source table timestamp //
// field, finding any records that need processed. //
// query will be for: timestamp > lastRun AND timestamp <= thisRun. //
// then thisRun will be stored, so the next run shouldn't find any records from thisRun. //
///////////////////////////////////////////////////////////////////////////////////////////////////
queryFilter.addCriteria(new QFilterCriteria()
.withFieldName(runBackendStepInput.getValueString(RunProcessAction.BASEPULL_TIMESTAMP_FIELD))
.withOperator(QCriteriaOperator.GREATER_THAN)
.withValues(List.of(getLastRunTimeString(runBackendStepInput))));
queryFilter.addCriteria(new QFilterCriteria()
.withFieldName(runBackendStepInput.getValueString(RunProcessAction.BASEPULL_TIMESTAMP_FIELD))
.withOperator(QCriteriaOperator.LESS_THAN_OR_EQUALS)
.withValues(List.of(getThisRunTimeString(runBackendStepInput))));
queryFilter.addOrderBy(new QFilterOrderBy(runBackendStepInput.getValueString(RunProcessAction.BASEPULL_TIMESTAMP_FIELD)));
return (queryFilter);
}
/*******************************************************************************
**
*******************************************************************************/
protected String getLastRunTimeString(RunBackendStepInput runBackendStepInput) throws QException
{
return (runBackendStepInput.getBasepullLastRunTime().toString());
}
/*******************************************************************************
**
*******************************************************************************/
protected String getThisRunTimeString(RunBackendStepInput runBackendStepInput) throws QException
{
return (runBackendStepInput.getValueInstant(RunProcessAction.BASEPULL_THIS_RUNTIME_KEY).toString());
}
}

View File

@ -26,6 +26,7 @@ import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
/*******************************************************************************
@ -49,6 +50,18 @@ public abstract class AbstractExtractStep implements BackendStep
/*******************************************************************************
** Allow subclasses to do an action before the run begins.
*******************************************************************************/
public void preRun(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
////////////////////////
// noop in base class //
////////////////////////
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -54,6 +54,20 @@ public class ExtractViaQueryStep extends AbstractExtractStep
{
public static final String FIELD_SOURCE_TABLE = "sourceTable";
private QQueryFilter queryFilter;
/*******************************************************************************
**
*******************************************************************************/
@Override
public void preRun(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
super.preRun(runBackendStepInput, runBackendStepOutput);
queryFilter = getQueryFilter(runBackendStepInput);
}
/*******************************************************************************
@ -66,7 +80,7 @@ public class ExtractViaQueryStep extends AbstractExtractStep
QueryInput queryInput = new QueryInput(runBackendStepInput.getInstance());
queryInput.setSession(runBackendStepInput.getSession());
queryInput.setTableName(runBackendStepInput.getValueString(FIELD_SOURCE_TABLE));
queryInput.setFilter(getQueryFilter(runBackendStepInput));
queryInput.setFilter(queryFilter);
queryInput.setRecordPipe(getRecordPipe());
queryInput.setLimit(getLimit());
queryInput.setAsyncJobCallback(runBackendStepInput.getAsyncJobCallback());
@ -88,7 +102,7 @@ public class ExtractViaQueryStep extends AbstractExtractStep
CountInput countInput = new CountInput(runBackendStepInput.getInstance());
countInput.setSession(runBackendStepInput.getSession());
countInput.setTableName(runBackendStepInput.getValueString(FIELD_SOURCE_TABLE));
countInput.setFilter(getQueryFilter(runBackendStepInput));
countInput.setFilter(queryFilter);
CountOutput countOutput = new CountAction().execute(countInput);
return (countOutput.getCount());
}

View File

@ -37,6 +37,7 @@ 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.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
@ -49,6 +50,9 @@ public class LoadViaInsertOrUpdateStep extends AbstractLoadStep
{
public static final String FIELD_DESTINATION_TABLE = "destinationTable";
protected List<QRecord> recordsToInsert = null;
protected List<QRecord> recordsToUpdate = null;
/*******************************************************************************
@ -58,22 +62,20 @@ public class LoadViaInsertOrUpdateStep extends AbstractLoadStep
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
QTableMetaData tableMetaData = runBackendStepInput.getInstance().getTable(runBackendStepInput.getValueString(FIELD_DESTINATION_TABLE));
List<QRecord> recordsToInsert = new ArrayList<>();
List<QRecord> recordsToUpdate = new ArrayList<>();
for(QRecord record : runBackendStepInput.getRecords())
{
if(record.getValue(tableMetaData.getPrimaryKeyField()) == null)
{
recordsToInsert.add(record);
}
else
{
recordsToUpdate.add(record);
}
}
evaluateRecords(runBackendStepInput);
insertAndUpdateRecords(runBackendStepInput, runBackendStepOutput);
}
if(!recordsToInsert.isEmpty())
/*******************************************************************************
**
*******************************************************************************/
public void insertAndUpdateRecords(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
QTableMetaData tableMetaData = runBackendStepInput.getInstance().getTable(runBackendStepInput.getValueString(FIELD_DESTINATION_TABLE));
if(CollectionUtils.nullSafeHasContents(recordsToInsert))
{
InsertInput insertInput = new InsertInput(runBackendStepInput.getInstance());
insertInput.setSession(runBackendStepInput.getSession());
@ -84,7 +86,7 @@ public class LoadViaInsertOrUpdateStep extends AbstractLoadStep
runBackendStepOutput.getRecords().addAll(insertOutput.getRecords());
}
if(!recordsToUpdate.isEmpty())
if(CollectionUtils.nullSafeHasContents(recordsToUpdate))
{
UpdateInput updateInput = new UpdateInput(runBackendStepInput.getInstance());
updateInput.setSession(runBackendStepInput.getSession());
@ -110,4 +112,27 @@ public class LoadViaInsertOrUpdateStep extends AbstractLoadStep
return (Optional.of(new InsertAction().openTransaction(insertInput)));
}
/*******************************************************************************
**
*******************************************************************************/
protected void evaluateRecords(RunBackendStepInput runBackendStepInput) throws QException
{
QTableMetaData tableMetaData = runBackendStepInput.getInstance().getTable(runBackendStepInput.getValueString(FIELD_DESTINATION_TABLE));
recordsToInsert = new ArrayList<>();
recordsToUpdate = new ArrayList<>();
for(QRecord record : runBackendStepInput.getRecords())
{
if(record.getValue(tableMetaData.getPrimaryKeyField()) == null)
{
recordsToInsert.add(record);
}
else
{
recordsToUpdate.add(record);
}
}
}
}

View File

@ -28,6 +28,7 @@ import java.util.Optional;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
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.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
@ -63,6 +64,7 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe
RecordPipe recordPipe = new RecordPipe();
AbstractExtractStep extractStep = getExtractStep(runBackendStepInput);
extractStep.setRecordPipe(recordPipe);
extractStep.preRun(runBackendStepInput, runBackendStepOutput);
AbstractTransformStep transformStep = getTransformStep(runBackendStepInput);
AbstractLoadStep loadStep = getLoadStep(runBackendStepInput);
@ -88,14 +90,26 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe
updateRecordsWithDisplayValuesAndPossibleValues(runBackendStepInput, loadedRecordList);
runBackendStepOutput.setRecords(loadedRecordList);
////////////////////////////////////////////////////////////////////////////////////////////////////
// get the process summary from the ... transform step? the load step? each knows some... todo? //
////////////////////////////////////////////////////////////////////////////////////////////////////
runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY, transformStep.doGetProcessSummary(runBackendStepOutput, true));
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// get the process summary from the load step, if it's a summary-provider -- else, use the transform step (which is always a provider) //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(loadStep instanceof ProcessSummaryProviderInterface provider)
{
runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY, provider.doGetProcessSummary(runBackendStepOutput, true));
}
else
{
runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY, transformStep.doGetProcessSummary(runBackendStepOutput, true));
}
transformStep.postRun(runBackendStepInput, runBackendStepOutput);
loadStep.postRun(runBackendStepInput, runBackendStepOutput);
//////////////////////////////////////////////////////////////////////////////
// set the flag to state that the basepull timestamp should be updated now. //
//////////////////////////////////////////////////////////////////////////////
runBackendStepOutput.addValue(RunProcessAction.BASEPULL_READY_TO_UPDATE_TIMESTAMP_FIELD, true);
/////////////////////
// commit the work //
/////////////////////

View File

@ -30,6 +30,7 @@ import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamed.StreamedETLProcess;
import org.apache.logging.log4j.LogManager;
@ -66,6 +67,12 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
return;
}
if(runBackendStepInput.getFrontendStepBehavior() != null && runBackendStepInput.getFrontendStepBehavior().equals(RunProcessInput.FrontendStepBehavior.SKIP))
{
LOG.info("Skipping preview because frontent behavior is [" + RunProcessInput.FrontendStepBehavior.SKIP + "].");
return;
}
/////////////////////////////////////////////////////////////////
// if we're running inside an automation, then skip this step. //
/////////////////////////////////////////////////////////////////
@ -75,13 +82,21 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
return;
}
///////////////////////////////////////////
// request a count from the extract step //
///////////////////////////////////////////
//////////////////////////////////////////
// set up the extract & transform steps //
//////////////////////////////////////////
AbstractExtractStep extractStep = getExtractStep(runBackendStepInput);
Integer recordCount = extractStep.doCount(runBackendStepInput);
RecordPipe recordPipe = new RecordPipe();
extractStep.setLimit(limit);
extractStep.setRecordPipe(recordPipe);
extractStep.preRun(runBackendStepInput, runBackendStepOutput);
Integer recordCount = extractStep.doCount(runBackendStepInput);
runBackendStepOutput.addValue(StreamedETLProcess.FIELD_RECORD_COUNT, recordCount);
AbstractTransformStep transformStep = getTransformStep(runBackendStepInput);
transformStep.preRun(runBackendStepInput, runBackendStepOutput);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if the count is less than the normal limit here, and this process supports validation, then go straight to the validation step //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -93,16 +108,6 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
// return;
// }
////////////////////////////////////////////////////////
// proceed with a doing a limited extract & transform //
////////////////////////////////////////////////////////
RecordPipe recordPipe = new RecordPipe();
extractStep.setLimit(limit);
extractStep.setRecordPipe(recordPipe);
AbstractTransformStep transformStep = getTransformStep(runBackendStepInput);
transformStep.preRun(runBackendStepInput, runBackendStepOutput);
List<QRecord> previewRecordList = new ArrayList<>();
new AsyncRecordPipeLoop().run("StreamedETL>Preview>ExtractStep", PROCESS_OUTPUT_RECORD_LIST_LIMIT, recordPipe, (status) ->
{
@ -140,7 +145,6 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
/*******************************************************************************
**
*******************************************************************************/
@ -154,7 +158,7 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
///////////////////////////////////////////////////////////////////////
// make streamed input & output objects from the run input & outputs //
///////////////////////////////////////////////////////////////////////
StreamedBackendStepInput streamedBackendStepInput = new StreamedBackendStepInput(runBackendStepInput, qRecords);
StreamedBackendStepInput streamedBackendStepInput = new StreamedBackendStepInput(runBackendStepInput, qRecords);
StreamedBackendStepOutput streamedBackendStepOutput = new StreamedBackendStepOutput(runBackendStepOutput);
/////////////////////////////////////////////////////

View File

@ -85,6 +85,7 @@ public class StreamedETLValidateStep extends BaseStreamedETLStep implements Back
AbstractExtractStep extractStep = getExtractStep(runBackendStepInput);
extractStep.setLimit(null);
extractStep.setRecordPipe(recordPipe);
extractStep.preRun(runBackendStepInput, runBackendStepOutput);
AbstractTransformStep transformStep = getTransformStep(runBackendStepInput);
transformStep.preRun(runBackendStepInput, runBackendStepOutput);

View File

@ -23,11 +23,16 @@ package com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwit
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.processes.AbstractProcessMetaDataBuilder;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
@ -131,8 +136,8 @@ public class StreamedETLWithFrontendProcess
.withField(new QFieldMetaData(FIELD_DESTINATION_TABLE, QFieldType.STRING).withDefaultValue(defaultFieldValues.get(FIELD_DESTINATION_TABLE)))
.withField(new QFieldMetaData(FIELD_SUPPORTS_FULL_VALIDATION, QFieldType.BOOLEAN).withDefaultValue(defaultFieldValues.getOrDefault(FIELD_SUPPORTS_FULL_VALIDATION, true)))
.withField(new QFieldMetaData(FIELD_DEFAULT_QUERY_FILTER, QFieldType.STRING).withDefaultValue(defaultFieldValues.get(FIELD_DEFAULT_QUERY_FILTER)))
.withField(new QFieldMetaData(FIELD_EXTRACT_CODE, QFieldType.STRING).withDefaultValue(new QCodeReference(extractStepClass)))
.withField(new QFieldMetaData(FIELD_TRANSFORM_CODE, QFieldType.STRING).withDefaultValue(new QCodeReference(transformStepClass)))
.withField(new QFieldMetaData(FIELD_EXTRACT_CODE, QFieldType.STRING).withDefaultValue(extractStepClass == null ? null : new QCodeReference(extractStepClass)))
.withField(new QFieldMetaData(FIELD_TRANSFORM_CODE, QFieldType.STRING).withDefaultValue(transformStepClass == null ? null : new QCodeReference(transformStepClass)))
.withField(new QFieldMetaData(FIELD_PREVIEW_MESSAGE, QFieldType.STRING).withDefaultValue(defaultFieldValues.getOrDefault(FIELD_PREVIEW_MESSAGE, DEFAULT_PREVIEW_MESSAGE_FOR_INSERT)))
);
@ -153,7 +158,7 @@ public class StreamedETLWithFrontendProcess
.withName(STEP_NAME_EXECUTE)
.withCode(new QCodeReference(StreamedETLExecuteStep.class))
.withInputData(new QFunctionInputMetaData()
.withField(new QFieldMetaData(FIELD_LOAD_CODE, QFieldType.STRING).withDefaultValue(new QCodeReference(loadStepClass))))
.withField(new QFieldMetaData(FIELD_LOAD_CODE, QFieldType.STRING).withDefaultValue(loadStepClass == null ? null : new QCodeReference(loadStepClass))))
.withOutputMetaData(new QFunctionOutputMetaData()
.withField(new QFieldMetaData(FIELD_PROCESS_SUMMARY, QFieldType.STRING))
);
@ -169,4 +174,204 @@ public class StreamedETLWithFrontendProcess
.addStep(executeStep)
.addStep(resultStep);
}
/*******************************************************************************
**
*******************************************************************************/
public static Builder processMetaDataBuilder()
{
return (new Builder(defineProcessMetaData(null, null, null, Collections.emptyMap())));
}
/*******************************************************************************
**
*******************************************************************************/
public static class Builder extends AbstractProcessMetaDataBuilder
{
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public Builder(QProcessMetaData processMetaData)
{
super(processMetaData);
}
/*******************************************************************************
** Fluent setter for extractStepClass
**
*******************************************************************************/
public Builder withExtractStepClass(Class<? extends AbstractExtractStep> extractStepClass)
{
setInputFieldDefaultValue(FIELD_EXTRACT_CODE, new QCodeReference(extractStepClass));
return (this);
}
/*******************************************************************************
** Fluent setter for transformStepClass
**
*******************************************************************************/
public Builder withTransformStepClass(Class<? extends AbstractTransformStep> transformStepClass)
{
setInputFieldDefaultValue(FIELD_TRANSFORM_CODE, new QCodeReference(transformStepClass));
return (this);
}
/*******************************************************************************
** Fluent setter for loadStepClass
**
*******************************************************************************/
public Builder withLoadStepClass(Class<? extends AbstractLoadStep> loadStepClass)
{
setInputFieldDefaultValue(FIELD_LOAD_CODE, new QCodeReference(loadStepClass));
return (this);
}
/*******************************************************************************
** Fluent setter for sourceTable
**
*******************************************************************************/
public Builder withSourceTable(String sourceTable)
{
setInputFieldDefaultValue(FIELD_SOURCE_TABLE, sourceTable);
return (this);
}
/*******************************************************************************
** Fluent setter for destinationTable
**
*******************************************************************************/
public Builder withDestinationTable(String destinationTable)
{
setInputFieldDefaultValue(FIELD_DESTINATION_TABLE, destinationTable);
return (this);
}
/*******************************************************************************
** Fluent setter for supportsFullValidation
**
*******************************************************************************/
public Builder withSupportsFullValidation(Boolean supportsFullValidation)
{
setInputFieldDefaultValue(FIELD_SUPPORTS_FULL_VALIDATION, supportsFullValidation);
return (this);
}
/*******************************************************************************
** Fluent setter for doFullValidation
**
*******************************************************************************/
public Builder withDoFullValidation(Boolean doFullValidation)
{
setInputFieldDefaultValue(FIELD_DO_FULL_VALIDATION, doFullValidation);
return (this);
}
/*******************************************************************************
** Fluent setter for defaultQueryFilter
**
*******************************************************************************/
public Builder withDefaultQueryFilter(QQueryFilter defaultQueryFilter)
{
setInputFieldDefaultValue(FIELD_DEFAULT_QUERY_FILTER, defaultQueryFilter);
return (this);
}
/*******************************************************************************
** Fluent setter for previewMessage
**
*******************************************************************************/
public Builder withPreviewMessage(String previewMessage)
{
setInputFieldDefaultValue(FIELD_PREVIEW_MESSAGE, previewMessage);
return (this);
}
/*******************************************************************************
** Fluent setter for name
**
*******************************************************************************/
public Builder withName(String name)
{
processMetaData.setName(name);
return (this);
}
/*******************************************************************************
** Fluent setter for label
**
*******************************************************************************/
public Builder withLabel(String name)
{
processMetaData.setLabel(name);
return (this);
}
/*******************************************************************************
** Fluent setter for tableName
**
*******************************************************************************/
public Builder withTableName(String tableName)
{
processMetaData.setTableName(tableName);
return (this);
}
/*******************************************************************************
** Fluent setter for icon
**
*******************************************************************************/
public Builder withIcon(QIcon icon)
{
processMetaData.setIcon(icon);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public Builder withReviewStepRecordFields(List<QFieldMetaData> fieldList)
{
QFrontendStepMetaData reviewStep = processMetaData.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_REVIEW);
for(QFieldMetaData fieldMetaData : fieldList)
{
reviewStep.withRecordListField(fieldMetaData);
}
return (this);
}
}
}

View File

@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.processes.implementations.mock;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
@ -64,6 +65,7 @@ public class MockBackendStep implements BackendStep
runBackendStepOutput.setValues(runBackendStepInput.getValues());
runBackendStepOutput.addValue(FIELD_MOCK_VALUE, MOCK_VALUE);
runBackendStepOutput.addValue("noOfPeopleGreeted", runBackendStepInput.getRecords().size());
runBackendStepOutput.addValue(RunProcessAction.BASEPULL_READY_TO_UPDATE_TIMESTAMP_FIELD, true);
if("there".equalsIgnoreCase(runBackendStepInput.getValueString(FIELD_GREETING_SUFFIX)))
{

View File

@ -37,6 +37,7 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutp
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
@ -46,6 +47,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
*******************************************************************************/
public class ExecuteReportStep implements BackendStep
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
@ -70,10 +75,9 @@ public class ExecuteReportStep implements BackendStep
new GenerateReportAction().execute(reportInput);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmm").withZone(ZoneId.systemDefault());
String datePart = formatter.format(Instant.now());
String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, report);
runBackendStepOutput.addValue("downloadFileName", report.getLabel() + " " + datePart + ".xlsx");
runBackendStepOutput.addValue("downloadFileName", downloadFileBaseName + ".xlsx");
runBackendStepOutput.addValue("serverFilePath", tmpFile.getCanonicalPath());
}
}
@ -82,4 +86,26 @@ public class ExecuteReportStep implements BackendStep
throw (new QException("Error running report", e));
}
}
/*******************************************************************************
**
*******************************************************************************/
private String getDownloadFileBaseName(RunBackendStepInput runBackendStepInput, QReportMetaData report)
{
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HHmm").withZone(ZoneId.systemDefault());
String datePart = formatter.format(Instant.now());
String downloadFileBaseName = runBackendStepInput.getValueString("downloadFileBaseName");
if(!StringUtils.hasContent(downloadFileBaseName))
{
downloadFileBaseName = report.getLabel();
}
downloadFileBaseName = downloadFileBaseName.replaceAll("/", "-");
return (downloadFileBaseName + " - " + datePart);
}
}

View File

@ -0,0 +1,124 @@
/*
* 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.processes.implementations.reports;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
** Version of PrepareReportStep for a report that runs off a single record.
*******************************************************************************/
public class PrepareReportForRecordStep extends PrepareReportStep
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
super.run(runBackendStepInput, runBackendStepOutput);
//////////////////////////////////////////////////////////////////////////////////
// look for the recordId having been posted to the process - error if not found //
//////////////////////////////////////////////////////////////////////////////////
Serializable recordId = null;
if("recordIds".equals(runBackendStepInput.getValueString("recordsParam")))
{
String recordIdsString = runBackendStepInput.getValueString("recordIds");
String[] recordIdsArray = recordIdsString.split(",");
if(recordIdsArray.length != 1)
{
throw (new QUserFacingException("Exactly 1 record must be selected as input to this report."));
}
recordId = recordIdsArray[0];
}
else
{
throw (new QUserFacingException("No record was selected as input to this report."));
}
/////////////////////////////////////////////////////////////////////////////////////////////
// look for the recordI input field on the process - put the input recordId in that field. //
// then remove that input field from the process's inputFieldList //
/////////////////////////////////////////////////////////////////////////////////////////////
@SuppressWarnings("unchecked")
ArrayList<QFieldMetaData> inputFieldList = (ArrayList<QFieldMetaData>) runBackendStepOutput.getValue("inputFieldList");
if(CollectionUtils.nullSafeHasContents(inputFieldList))
{
Iterator<QFieldMetaData> inputFieldListIterator = inputFieldList.iterator();
while(inputFieldListIterator.hasNext())
{
QFieldMetaData fieldMetaData = inputFieldListIterator.next();
if(fieldMetaData.getName().equals(RunReportForRecordProcess.FIELD_RECORD_ID))
{
runBackendStepOutput.addValue(RunReportForRecordProcess.FIELD_RECORD_ID, recordId);
inputFieldListIterator.remove();
runBackendStepOutput.addValue("inputFieldList", inputFieldList);
break;
}
}
}
GetInput getInput = new GetInput(runBackendStepInput.getInstance());
getInput.setSession(runBackendStepInput.getSession());
getInput.setTableName(runBackendStepInput.getTableName());
getInput.setPrimaryKey(recordId);
getInput.setShouldGenerateDisplayValues(true);
GetOutput getOutput = new GetAction().execute(getInput);
QRecord record = getOutput.getRecord();
if(record == null)
{
throw (new QUserFacingException("The selected record for the report was not found."));
}
String reportName = runBackendStepInput.getValueString("reportName");
QReportMetaData report = runBackendStepInput.getInstance().getReport(reportName);
// runBackendStepOutput.addValue("downloadFileBaseName", runBackendStepInput.getTable().getLabel() + " " + record.getRecordLabel());
runBackendStepOutput.addValue("downloadFileBaseName", report.getLabel() + " - " + record.getRecordLabel());
/////////////////////////////////////////////////////////////////////////////////////
// if there are no more input fields, then remove the INPUT step from the process. //
/////////////////////////////////////////////////////////////////////////////////////
inputFieldList = (ArrayList<QFieldMetaData>) runBackendStepOutput.getValue("inputFieldList");
if(!CollectionUtils.nullSafeHasContents(inputFieldList))
{
removeInputStepFromProcess(runBackendStepOutput);
}
}
}

View File

@ -43,6 +43,10 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
*******************************************************************************/
public class PrepareReportStep implements BackendStep
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
@ -68,12 +72,22 @@ public class PrepareReportStep implements BackendStep
}
else
{
//////////////////////////////////////////////////////////////
// no input? re-route the process to skip the input screen //
//////////////////////////////////////////////////////////////
List<String> stepList = new ArrayList<>(runBackendStepOutput.getProcessState().getStepList());
stepList.removeIf(s -> s.equals(BasicRunReportProcess.STEP_NAME_INPUT));
runBackendStepOutput.getProcessState().setStepList(stepList);
removeInputStepFromProcess(runBackendStepOutput);
}
}
/*******************************************************************************
**
*******************************************************************************/
protected void removeInputStepFromProcess(RunBackendStepOutput runBackendStepOutput)
{
//////////////////////////////////////////////////////////////
// no input? re-route the process to skip the input screen //
//////////////////////////////////////////////////////////////
List<String> stepList = new ArrayList<>(runBackendStepOutput.getProcessState().getStepList());
stepList.removeIf(s -> s.equals(BasicRunReportProcess.STEP_NAME_INPUT));
runBackendStepOutput.getProcessState().setStepList(stepList);
}
}

View File

@ -0,0 +1,167 @@
/*
* 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.processes.implementations.reports;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.processes.AbstractProcessMetaDataBuilder;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
/*******************************************************************************
** Definition for Basic process to run a report.
*******************************************************************************/
public class RunReportForRecordProcess
{
public static final String PROCESS_NAME = "reports.forRecord";
public static final String STEP_NAME_PREPARE = "prepare";
public static final String STEP_NAME_INPUT = "input";
public static final String STEP_NAME_EXECUTE = "execute";
public static final String STEP_NAME_ACCESS = "accessReport";
public static final String FIELD_REPORT_NAME = "reportName";
public static final String FIELD_RECORD_ID = "recordId";
/*******************************************************************************
**
*******************************************************************************/
public static Builder processMetaDataBuilder()
{
return (new Builder(defineProcessMetaData()));
}
/*******************************************************************************
**
*******************************************************************************/
private static QProcessMetaData defineProcessMetaData()
{
QStepMetaData prepareStep = new QBackendStepMetaData()
.withName(STEP_NAME_PREPARE)
.withCode(new QCodeReference(PrepareReportForRecordStep.class))
.withInputData(new QFunctionInputMetaData()
.withField(new QFieldMetaData(FIELD_REPORT_NAME, QFieldType.STRING))
.withField(new QFieldMetaData(FIELD_RECORD_ID, QFieldType.STRING)));
QStepMetaData inputStep = new QFrontendStepMetaData()
.withName(STEP_NAME_INPUT)
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM));
QStepMetaData executeStep = new QBackendStepMetaData()
.withName(STEP_NAME_EXECUTE)
.withCode(new QCodeReference(ExecuteReportStep.class))
.withInputData(new QFunctionInputMetaData()
.withField(new QFieldMetaData(FIELD_REPORT_NAME, QFieldType.STRING)));
QStepMetaData accessStep = new QFrontendStepMetaData()
.withName(STEP_NAME_ACCESS)
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.DOWNLOAD_FORM));
// .withViewField(new QFieldMetaData("outputFile", QFieldType.STRING))
// .withViewField(new QFieldMetaData("message", QFieldType.STRING));
return new QProcessMetaData()
.withName(PROCESS_NAME)
.addStep(prepareStep)
.addStep(inputStep)
.addStep(executeStep)
.addStep(accessStep);
}
/*******************************************************************************
**
*******************************************************************************/
public static class Builder extends AbstractProcessMetaDataBuilder
{
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public Builder(QProcessMetaData processMetaData)
{
super(processMetaData);
}
/*******************************************************************************
** Fluent setter for name
**
*******************************************************************************/
public Builder withProcessName(String name)
{
processMetaData.setName(name);
return (this);
}
/*******************************************************************************
** Fluent setter for tableName
**
*******************************************************************************/
public Builder withTableName(String tableName)
{
processMetaData.setTableName(tableName);
return (this);
}
/*******************************************************************************
** Fluent setter for icon
**
*******************************************************************************/
public Builder withIcon(QIcon icon)
{
processMetaData.setIcon(icon);
return (this);
}
/*******************************************************************************
** Fluent setter for reportName
**
*******************************************************************************/
public Builder withReportName(String reportName)
{
setInputFieldDefaultValue(RunReportForRecordProcess.FIELD_REPORT_NAME, reportName);
return (this);
}
}
}

View File

@ -0,0 +1,269 @@
/*
* 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.processes.utils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
/*******************************************************************************
** Utility methods for working with QQQ records and table actions inside user -
** defined QQQ processes steps.
*******************************************************************************/
public class GeneralProcessUtils
{
/*******************************************************************************
** For a list of sourceRecords,
** lookup records in the foreignTableName,
** that have their foreignTablePrimaryKeyName in the sourceTableForeignKeyFieldName on the sourceRecords.
**
** e.g., for a list of orders (with a clientId field), build a map of client.id => client record
** via getForeignRecordMap(input, orderList, "clientId", "client", "id")
*******************************************************************************/
public static Map<Serializable, QRecord> getForeignRecordMap(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException
{
Map<Serializable, QRecord> foreignRecordMap = new HashMap<>();
QueryInput queryInput = new QueryInput(parentActionInput.getInstance());
queryInput.setSession(parentActionInput.getSession());
queryInput.setTableName(foreignTableName);
List<Serializable> foreignIds = new ArrayList<>(sourceRecords.stream().map(r -> r.getValue(sourceTableForeignKeyFieldName)).toList());
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(foreignTablePrimaryKeyName, QCriteriaOperator.IN, foreignIds)));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
for(QRecord foreignRecord : queryOutput.getRecords())
{
foreignRecordMap.put(foreignRecord.getValue(foreignTablePrimaryKeyName), foreignRecord);
}
return foreignRecordMap;
}
/*******************************************************************************
** For a list of sourceRecords,
** lookup records in the foreignTableName,
** that have their foreignTableForeignKeyName in the sourceTableForeignKeyFieldName on the sourceRecords.
**
** e.g., for a list of orders, build a ListingHash of order.id => List(OrderLine records)
** via getForeignRecordListingHashMap(input, orderList, "id", "orderLine", "orderId")
*******************************************************************************/
public static ListingHash<Serializable, QRecord> getForeignRecordListingHashMap(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException
{
ListingHash<Serializable, QRecord> foreignRecordMap = new ListingHash<>();
QueryInput queryInput = new QueryInput(parentActionInput.getInstance());
queryInput.setSession(parentActionInput.getSession());
queryInput.setTableName(foreignTableName);
List<Serializable> foreignIds = new ArrayList<>(sourceRecords.stream().map(r -> r.getValue(sourceTableForeignKeyFieldName)).toList());
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(foreignTableForeignKeyName, QCriteriaOperator.IN, foreignIds)));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
for(QRecord foreignRecord : queryOutput.getRecords())
{
foreignRecordMap.add(foreignRecord.getValue(foreignTableForeignKeyName), foreignRecord);
}
return foreignRecordMap;
}
/*******************************************************************************
** For a list of sourceRecords,
** lookup records in the foreignTableName,
** that have their foreignTablePrimaryKeyName in the sourceTableForeignKeyFieldName on the sourceRecords.
** and set those foreign records as a value in the sourceRecords.
**
** e.g., for a list of orders (with a clientId field), setValue("client", QRecord(client));
** via addForeignRecordsToRecordList(input, orderList, "clientId", "client", "id")
*******************************************************************************/
public static void addForeignRecordsToRecordList(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException
{
Map<Serializable, QRecord> foreignRecordMap = getForeignRecordMap(parentActionInput, sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTablePrimaryKeyName);
for(QRecord sourceRecord : sourceRecords)
{
QRecord foreignRecord = foreignRecordMap.get(sourceRecord.getValue(sourceTableForeignKeyFieldName));
sourceRecord.setValue(foreignTableName, foreignRecord);
}
}
/*******************************************************************************
** For a list of sourceRecords,
** lookup records in the foreignTableName,
** that have their foreignTableForeignKeyName in the sourceTableForeignKeyFieldName on the sourceRecords.
**
** e.g., for a list of orders, setValue("orderLine", List(QRecord(orderLine)))
** via addForeignRecordsListToRecordList(input, orderList, "id", "orderLine", "orderId")
*******************************************************************************/
public static void addForeignRecordsListToRecordList(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException
{
ListingHash<Serializable, QRecord> foreignRecordMap = getForeignRecordListingHashMap(parentActionInput, sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTableForeignKeyName);
for(QRecord sourceRecord : sourceRecords)
{
List<QRecord> foreignRecordList = foreignRecordMap.get(sourceRecord.getValue(sourceTableForeignKeyFieldName));
if(foreignRecordList != null)
{
if(foreignRecordList instanceof Serializable s)
{
sourceRecord.setValue(foreignTableName, s);
}
else
{
sourceRecord.setValue(foreignTableName, new ArrayList<>(foreignRecordList));
}
}
}
}
/*******************************************************************************
** Run a query on tableName, for where fieldName equals fieldValue, and return
** the list of QRecords.
*******************************************************************************/
public static List<QRecord> getRecordListByField(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException
{
QueryInput queryInput = new QueryInput(parentActionInput.getInstance());
queryInput.setSession(parentActionInput.getSession());
queryInput.setTableName(tableName);
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, List.of(fieldValue))));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
return (queryOutput.getRecords());
}
/*******************************************************************************
** Query to get one record by a unique key value. That field can be the primary
** key, or any other field on the table. Note, if multiple rows do match the value,
** only 1 (determined in an unspecified way) is returned.
*******************************************************************************/
public static Optional<QRecord> getRecordById(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException
{
QueryInput queryInput = new QueryInput(parentActionInput.getInstance());
queryInput.setSession(parentActionInput.getSession());
queryInput.setTableName(tableName);
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, List.of(fieldValue))));
queryInput.setLimit(1);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
return (queryOutput.getRecords().stream().findFirst());
}
/*******************************************************************************
** Load all rows from a table.
**
** Note, this is inherently unsafe, if you were to call it on a table with
** too many rows... Caveat emptor.
*******************************************************************************/
public static List<QRecord> loadTable(AbstractActionInput parentActionInput, String tableName) throws QException
{
QueryInput queryInput = new QueryInput(parentActionInput.getInstance());
queryInput.setSession(parentActionInput.getSession());
queryInput.setTableName(tableName);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
return (queryOutput.getRecords());
}
/*******************************************************************************
** Load all rows from a table, into a map, keyed by the keyFieldName.
**
** Note - null values from the key field are NOT put in the map.
**
** If multiple values are found for the key, they'll squash each other, and only
** one random value will appear.
**
** Also, note, this is inherently unsafe, if you were to call it on a table with
** too many rows... Caveat emptor.
*******************************************************************************/
public static Map<Serializable, QRecord> loadTableToMap(AbstractActionInput parentActionInput, String tableName, String keyFieldName) throws QException
{
QueryInput queryInput = new QueryInput(parentActionInput.getInstance());
queryInput.setSession(parentActionInput.getSession());
queryInput.setTableName(tableName);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
List<QRecord> records = queryOutput.getRecords();
Map<Serializable, QRecord> map = new HashMap<>();
for(QRecord record : records)
{
Serializable value = record.getValue(keyFieldName);
if(value != null)
{
map.put(value, record);
}
}
return (map);
}
/*******************************************************************************
** Load all rows from a table, into a ListingHash, keyed by the keyFieldName.
**
** Note - null values from the key field are NOT put in the map.
**
** The ordering of the records is not specified.
**
** Also, note, this is inherently unsafe, if you were to call it on a table with
** too many rows... Caveat emptor.
*******************************************************************************/
public static ListingHash<Serializable, QRecord> loadTableToListingHash(AbstractActionInput parentActionInput, String tableName, String keyFieldName) throws QException
{
QueryInput queryInput = new QueryInput(parentActionInput.getInstance());
queryInput.setSession(parentActionInput.getSession());
queryInput.setTableName(tableName);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
List<QRecord> records = queryOutput.getRecords();
ListingHash<Serializable, QRecord> map = new ListingHash<>();
for(QRecord record : records)
{
Serializable value = record.getValue(keyFieldName);
if(value != null)
{
map.add(value, record);
}
}
return (map);
}
}

View File

@ -23,9 +23,10 @@ package com.kingsrook.qqq.backend.core.utils;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.Instant;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
@ -230,34 +231,86 @@ public class JsonUtils
** Convert a json object into a QRecord
**
*******************************************************************************/
public static QRecord parseQRecord(JSONObject jsonObject, Map<String, QFieldMetaData> fields)
public static QRecord parseQRecord(JSONObject jsonObject, Map<String, QFieldMetaData> fields, boolean useBackendFieldNames)
{
QRecord record = new QRecord();
FIELDS_LOOP:
for(String fieldName : fields.keySet())
{
QFieldMetaData metaData = fields.get(fieldName);
String backendName = metaData.getBackendName() != null ? metaData.getBackendName() : fieldName;
switch(metaData.getType())
String originalBackendName = null;
try
{
case INTEGER -> record.setValue(fieldName, jsonObject.optInt(backendName));
case DECIMAL -> record.setValue(fieldName, jsonObject.optBigDecimal(backendName, null));
case BOOLEAN -> record.setValue(fieldName, jsonObject.optBoolean(backendName));
case DATE_TIME ->
QFieldMetaData metaData = fields.get(fieldName);
String backendName = fieldName;
if(useBackendFieldNames)
{
String dateTimeString = jsonObject.optString(backendName);
if(StringUtils.hasContent(dateTimeString))
backendName = metaData.getBackendName() != null ? metaData.getBackendName() : fieldName;
}
originalBackendName = backendName;
/////////////////////////////////////////////////////////////////////////////////////////////////
// if the field backend name has dots in it, interpret that to mean traversal down sub-objects //
/////////////////////////////////////////////////////////////////////////////////////////////////
JSONObject jsonObjectToUse = jsonObject;
if(backendName.contains("."))
{
ArrayList<String> levels = new ArrayList<>(List.of(backendName.split("\\.")));
backendName = levels.remove(levels.size() - 1);
for(String level : levels)
{
try
{
record.setValue(fieldName, LocalDateTime.parse(dateTimeString, DateTimeFormatter.ISO_ZONED_DATE_TIME));
jsonObjectToUse = jsonObjectToUse.optJSONObject(level);
if(jsonObjectToUse == null)
{
continue FIELDS_LOOP;
}
}
catch(DateTimeParseException dtpe1)
catch(Exception e)
{
record.setValue(fieldName, LocalDateTime.parse(dateTimeString, DateTimeFormatter.ISO_DATE_TIME));
continue FIELDS_LOOP;
}
}
}
default -> record.setValue(fieldName, jsonObject.optString(backendName));
if(jsonObjectToUse.isNull(backendName))
{
record.setValue(fieldName, null);
continue;
}
switch(metaData.getType())
{
case INTEGER -> record.setValue(fieldName, jsonObjectToUse.optInt(backendName));
case DECIMAL -> record.setValue(fieldName, jsonObjectToUse.optBigDecimal(backendName, null));
case BOOLEAN -> record.setValue(fieldName, jsonObjectToUse.optBoolean(backendName));
case DATE_TIME ->
{
String dateTimeString = jsonObjectToUse.optString(backendName);
if(StringUtils.hasContent(dateTimeString))
{
Instant instant = ValueUtils.getValueAsInstant(dateTimeString);
record.setValue(fieldName, instant);
}
}
case DATE ->
{
String dateString = jsonObjectToUse.optString(backendName);
if(StringUtils.hasContent(dateString))
{
LocalDate localDate = ValueUtils.getValueAsLocalDate(dateString);
record.setValue(fieldName, localDate);
}
}
default -> record.setValue(fieldName, jsonObjectToUse.optString(backendName));
}
}
catch(Exception e)
{
LOG.debug("Caught exception parsing field [" + fieldName + "] as [" + originalBackendName + "]", e);
}
}

View File

@ -30,6 +30,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Calendar;
@ -498,6 +499,22 @@ public class ValueUtils
}
else
{
try
{
return LocalDateTime.parse(s, DateTimeFormatter.ISO_ZONED_DATE_TIME).toInstant(ZoneOffset.UTC);
}
catch(DateTimeParseException e2)
{
try
{
return LocalDateTime.parse(s, DateTimeFormatter.ISO_DATE_TIME).toInstant(ZoneOffset.UTC);
}
catch(Exception e3)
{
// just throw the original
}
}
throw (e);
}
}

View File

@ -23,19 +23,27 @@ package com.kingsrook.qqq.backend.core.actions.processes;
import java.io.Serializable;
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
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.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.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
@ -47,6 +55,7 @@ import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackend
import com.kingsrook.qqq.backend.core.state.StateType;
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.Test;
@ -70,6 +79,80 @@ public class RunProcessTest
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testBasepull() throws QException
{
TestCallback callback = new TestCallback();
RunProcessInput request = new RunProcessInput(TestUtils.defineInstance());
request.setSession(TestUtils.getMockSession());
request.setProcessName(TestUtils.PROCESS_NAME_BASEPULL);
request.setCallback(callback);
RunProcessOutput result = new RunProcessAction().execute(request);
assertNotNull(result);
//////////////////////////////////////////////////////////////////////////////////////////
// get the last run time and 'this' run time - because the definition states that if no //
// rows found, the last runtime timestamp should be for 24 hours ago //
//////////////////////////////////////////////////////////////////////////////////////////
Instant lastRunTime = (Instant) result.getValues().get(RunProcessAction.BASEPULL_LAST_RUNTIME_KEY);
Instant thisRunTime = (Instant) result.getValues().get(RunProcessAction.BASEPULL_THIS_RUNTIME_KEY);
assertTrue(thisRunTime.isAfter(lastRunTime), "new run time should be after last run time.");
DayOfWeek lastRunTimeDayOfWeek = lastRunTime.atZone(ZoneId.systemDefault()).getDayOfWeek();
DayOfWeek thisRunTimeDayOfWeek = thisRunTime.atZone(ZoneId.systemDefault()).getDayOfWeek();
thisRunTimeDayOfWeek = thisRunTimeDayOfWeek.minus(1);
assertEquals(lastRunTimeDayOfWeek.getValue(), thisRunTimeDayOfWeek.getValue(), "last and this run times should be the same day after subtracting a day");
///////////////////////////////////////////////
// make sure new stamp stored in backend too //
///////////////////////////////////////////////
assertEquals(thisRunTime, getBasepullLastRunTime(), "last run time should be properly stored in backend");
////////////////////////////////////////////////////
// run the process one more time and check values //
////////////////////////////////////////////////////
result = new RunProcessAction().execute(request);
assertNotNull(result);
////////////////////////////////
// this should still be after //
////////////////////////////////
lastRunTime = (Instant) result.getValues().get(RunProcessAction.BASEPULL_LAST_RUNTIME_KEY);
thisRunTime = (Instant) result.getValues().get(RunProcessAction.BASEPULL_THIS_RUNTIME_KEY);
assertTrue(thisRunTime.isAfter(lastRunTime), "new run time should be after last run time.");
///////////////////////////////////////////////
// make sure new stamp stored in backend too //
///////////////////////////////////////////////
assertEquals(thisRunTime, getBasepullLastRunTime(), "last run time should be properly stored in backend");
}
/*******************************************************************************
**
*******************************************************************************/
private Instant getBasepullLastRunTime() throws QException
{
QueryInput queryInput = new QueryInput(TestUtils.defineInstance());
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria()
.withFieldName(TestUtils.BASEPULL_KEY_FIELD_NAME)
.withOperator(QCriteriaOperator.EQUALS)
.withValues(List.of(TestUtils.PROCESS_NAME_BASEPULL))));
queryInput.setSession(TestUtils.getMockSession());
queryInput.setTableName(TestUtils.TABLE_NAME_BASEPULL);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
assertNotNull(queryOutput);
assertEquals(1, queryOutput.getRecords().size(), "Should have one record");
return (ValueUtils.getValueAsInstant(queryOutput.getRecords().get(0).getValue(TestUtils.BASEPULL_LAST_RUN_TIME_FIELD_NAME)));
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -0,0 +1,177 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.Log4jCodeExecutionLogger;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.NoopCodeExecutionLogger;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger;
import com.kingsrook.qqq.backend.core.exceptions.QCodeException;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
/*******************************************************************************
** Unit test for ExecuteCodeAction
*******************************************************************************/
class ExecuteCodeActionTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
@AfterEach
void beforeAndAfterEach()
{
MemoryRecordStore.getInstance().reset();
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
ExecuteCodeInput executeCodeInput = setupInput(qInstance, Map.of("x", 4), new NoopCodeExecutionLogger());
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
assertEquals(16, executeCodeOutput.getOutput());
}
/*******************************************************************************
**
*******************************************************************************/
private ExecuteCodeInput setupInput(QInstance qInstance, Map<String, Serializable> context, QCodeExecutionLoggerInterface executionLogger)
{
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(qInstance);
executeCodeInput.setSession(new QSession());
executeCodeInput.setCodeReference(new QCodeReference(ScriptInJava.class, QCodeUsage.CUSTOMIZER));
executeCodeInput.setContext(context);
executeCodeInput.setExecutionLogger(executionLogger);
return executeCodeInput;
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testLog4jLogger() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
ExecuteCodeInput executeCodeInput = setupInput(qInstance, Map.of("x", 4), new Log4jCodeExecutionLogger());
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
assertEquals(16, executeCodeOutput.getOutput());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTableLogger() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
new ScriptsMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
ExecuteCodeInput executeCodeInput = setupInput(qInstance, Map.of("x", 4), new StoreScriptLogAndScriptLogLineExecutionLogger(1701, 1702));
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
assertEquals(16, executeCodeOutput.getOutput());
List<QRecord> scriptLogRecords = TestUtils.queryTable(qInstance, "scriptLog");
List<QRecord> scriptLogLineRecords = TestUtils.queryTable(qInstance, "scriptLogLine");
assertEquals(1, scriptLogRecords.size());
assertEquals(1701, scriptLogRecords.get(0).getValueInteger("scriptId"));
assertEquals(1702, scriptLogRecords.get(0).getValueInteger("scriptRevisionId"));
assertEquals(1, scriptLogLineRecords.size());
assertEquals(1, scriptLogLineRecords.get(0).getValueInteger("scriptLogId"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testException()
{
QInstance qInstance = TestUtils.defineInstance();
ExecuteCodeInput executeCodeInput = setupInput(qInstance, Map.of(), new NoopCodeExecutionLogger());
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
assertThrows(QCodeException.class, () ->
{
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
});
}
/*******************************************************************************
**
*******************************************************************************/
public static class ScriptInJava implements Function<Map<String, Object>, Serializable>
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public Serializable apply(Map<String, Object> context)
{
((QCodeExecutionLoggerInterface) context.get("logger")).log("Test a log");
int x = ValueUtils.getValueAsInteger(context.get("x"));
return (x * x);
}
}
}

View File

@ -0,0 +1,306 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptOutput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.StoreAssociatedScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.StoreAssociatedScriptOutput;
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.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/*******************************************************************************
** Unit test for RunAssociatedScriptAction
*******************************************************************************/
class RunAssociatedScriptActionTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
QInstance instance = setupInstance();
insertScript(instance, 1, """
return "Hello";
""");
RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(instance);
runAssociatedScriptInput.setSession(new QSession());
runAssociatedScriptInput.setInputValues(Map.of());
runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference()
.withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withRecordPrimaryKey(1)
.withFieldName("testScriptId")
);
RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput();
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ok - since the core module doesn't have the javascript language support module as a dep, this action will fail - but at least we can confirm it fails with this specific exception! //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput))
.isInstanceOf(QException.class)
.hasRootCauseInstanceOf(ClassNotFoundException.class)
.hasRootCauseMessage("com.kingsrook.qqq.languages.javascript.QJavaScriptExecutor");
}
/*******************************************************************************
**
*******************************************************************************/
private QInstance setupInstance() throws QException
{
QInstance instance = TestUtils.defineInstance();
QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withField(new QFieldMetaData("testScriptId", QFieldType.INTEGER))
.withAssociatedScript(new AssociatedScript()
.withScriptTypeId(1)
.withFieldName("testScriptId")
);
new ScriptsMetaDataProvider().defineAll(instance, TestUtils.MEMORY_BACKEND_NAME, null);
TestUtils.insertRecords(instance, table, List.of(
new QRecord().withValue("id", 1),
new QRecord().withValue("id", 2)
));
TestUtils.insertRecords(instance, instance.getTable("scriptType"), List.of(
new QRecord().withValue("id", 1).withValue("name", "Test Script Type")
));
return instance;
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testRecordNotFound() throws QException
{
QInstance instance = setupInstance();
RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(instance);
runAssociatedScriptInput.setSession(new QSession());
runAssociatedScriptInput.setInputValues(Map.of());
runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference()
.withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withRecordPrimaryKey(-9999)
.withFieldName("testScriptId")
);
RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput();
assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput))
.isInstanceOf(QNotFoundException.class)
.hasMessageMatching("The requested record.*was not found.*");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testNoScriptInRecord() throws QException
{
QInstance instance = setupInstance();
RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(instance);
runAssociatedScriptInput.setSession(new QSession());
runAssociatedScriptInput.setInputValues(Map.of());
runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference()
.withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withRecordPrimaryKey(1)
.withFieldName("testScriptId")
);
RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput();
assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput))
.isInstanceOf(QNotFoundException.class)
.hasMessageMatching("The input record.*does not have a script specified for.*");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testBadScriptIdInRecord() throws QException
{
QInstance instance = setupInstance();
UpdateInput updateInput = new UpdateInput(instance);
updateInput.setSession(new QSession());
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
updateInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("testScriptId", -9998)));
new UpdateAction().execute(updateInput);
RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(instance);
runAssociatedScriptInput.setSession(new QSession());
runAssociatedScriptInput.setInputValues(Map.of());
runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference()
.withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withRecordPrimaryKey(1)
.withFieldName("testScriptId")
);
RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput();
assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput))
.isInstanceOf(QNotFoundException.class)
.hasMessageMatching("The script for record .* was not found.*");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testNoCurrentScriptRevisionOnScript() throws QException
{
QInstance instance = setupInstance();
insertScript(instance, 1, """
return "Hello";
""");
GetInput getInput = new GetInput(instance);
getInput.setSession(new QSession());
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
getInput.setPrimaryKey(1);
GetOutput getOutput = new GetAction().execute(getInput);
Integer scriptId = getOutput.getRecord().getValueInteger("testScriptId");
UpdateInput updateInput = new UpdateInput(instance);
updateInput.setSession(new QSession());
updateInput.setTableName("script");
updateInput.setRecords(List.of(new QRecord().withValue("id", scriptId).withValue("currentScriptRevisionId", null)));
new UpdateAction().execute(updateInput);
RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(instance);
runAssociatedScriptInput.setSession(new QSession());
runAssociatedScriptInput.setInputValues(Map.of());
runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference()
.withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withRecordPrimaryKey(1)
.withFieldName("testScriptId")
);
RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput();
assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput))
.isInstanceOf(QNotFoundException.class)
.hasMessageMatching("The script for record .* does not have a current version.*");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testBadCurrentScriptRevisionOnScript() throws QException
{
QInstance instance = setupInstance();
insertScript(instance, 1, """
return "Hello";
""");
GetInput getInput = new GetInput(instance);
getInput.setSession(new QSession());
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
getInput.setPrimaryKey(1);
GetOutput getOutput = new GetAction().execute(getInput);
Integer scriptId = getOutput.getRecord().getValueInteger("testScriptId");
UpdateInput updateInput = new UpdateInput(instance);
updateInput.setSession(new QSession());
updateInput.setTableName("script");
updateInput.setRecords(List.of(new QRecord().withValue("id", scriptId).withValue("currentScriptRevisionId", 9997)));
new UpdateAction().execute(updateInput);
RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(instance);
runAssociatedScriptInput.setSession(new QSession());
runAssociatedScriptInput.setInputValues(Map.of());
runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference()
.withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withRecordPrimaryKey(1)
.withFieldName("testScriptId")
);
RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput();
assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput))
.isInstanceOf(QNotFoundException.class)
.hasMessageMatching("The current revision of the script for record .* was not found.*");
}
/*******************************************************************************
**
*******************************************************************************/
private void insertScript(QInstance instance, Serializable recordId, String code) throws QException
{
StoreAssociatedScriptInput storeAssociatedScriptInput = new StoreAssociatedScriptInput(instance);
storeAssociatedScriptInput.setSession(new QSession());
storeAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
storeAssociatedScriptInput.setRecordPrimaryKey(recordId);
storeAssociatedScriptInput.setCode(code);
storeAssociatedScriptInput.setFieldName("testScriptId");
StoreAssociatedScriptOutput storeAssociatedScriptOutput = new StoreAssociatedScriptOutput();
new StoreAssociatedScriptAction().run(storeAssociatedScriptInput, storeAssociatedScriptOutput);
}
}

View File

@ -0,0 +1,172 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.StoreAssociatedScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.StoreAssociatedScriptOutput;
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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.session.QSession;
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;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
/*******************************************************************************
** Unit test for StoreAssociatedScriptAction
*******************************************************************************/
class StoreAssociatedScriptActionTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
@AfterEach
void beforeAndAfterEach()
{
MemoryRecordStore.getInstance().reset();
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
QInstance instance = TestUtils.defineInstance();
QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withField(new QFieldMetaData("testScriptId", QFieldType.INTEGER))
.withAssociatedScript(new AssociatedScript()
.withScriptTypeId(1)
.withFieldName("testScriptId")
)
.withField(new QFieldMetaData("otherScriptId", QFieldType.INTEGER))
.withAssociatedScript(new AssociatedScript()
.withScriptTypeId(2)
.withFieldName("otherScriptId")
);
new ScriptsMetaDataProvider().defineAll(instance, TestUtils.MEMORY_BACKEND_NAME, null);
TestUtils.insertRecords(instance, table, List.of(
new QRecord().withValue("id", 1),
new QRecord().withValue("id", 2),
new QRecord().withValue("id", 3)
));
TestUtils.insertRecords(instance, instance.getTable("scriptType"), List.of(
new QRecord().withValue("id", 1).withValue("name", "Test Script"),
new QRecord().withValue("id", 2).withValue("name", "Other Script")
));
StoreAssociatedScriptInput storeAssociatedScriptInput = new StoreAssociatedScriptInput(instance);
storeAssociatedScriptInput.setSession(new QSession());
storeAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
storeAssociatedScriptInput.setRecordPrimaryKey(1);
storeAssociatedScriptInput.setCode("var i = 0;");
storeAssociatedScriptInput.setCommitMessage("Test commit");
storeAssociatedScriptInput.setFieldName("testScriptId");
StoreAssociatedScriptOutput storeAssociatedScriptOutput = new StoreAssociatedScriptOutput();
///////////////////////////////////////////////
// insert 1st version of script for record 1 //
///////////////////////////////////////////////
new StoreAssociatedScriptAction().run(storeAssociatedScriptInput, storeAssociatedScriptOutput);
assertValueInField(instance, TestUtils.TABLE_NAME_PERSON_MEMORY, 1, "testScriptId", 1);
assertValueInField(instance, "script", 1, "currentScriptRevisionId", 1);
////////////////////////////////////////////
// add 2nd version of script for record 1 //
////////////////////////////////////////////
storeAssociatedScriptInput.setCode("var i = 1;");
storeAssociatedScriptInput.setCommitMessage("2nd commit");
new StoreAssociatedScriptAction().run(storeAssociatedScriptInput, storeAssociatedScriptOutput);
assertValueInField(instance, TestUtils.TABLE_NAME_PERSON_MEMORY, 1, "testScriptId", 1);
assertValueInField(instance, "script", 1, "currentScriptRevisionId", 2);
///////////////////////////////////////////////
// insert 1st version of script for record 3 //
///////////////////////////////////////////////
storeAssociatedScriptInput.setRecordPrimaryKey(3);
storeAssociatedScriptInput.setCode("var i = 2;");
storeAssociatedScriptInput.setCommitMessage("First Commit here");
new StoreAssociatedScriptAction().run(storeAssociatedScriptInput, storeAssociatedScriptOutput);
assertValueInField(instance, TestUtils.TABLE_NAME_PERSON_MEMORY, 3, "testScriptId", 2);
assertValueInField(instance, "script", 2, "currentScriptRevisionId", 3);
/////////////////////////////////////
// make sure no script on record 2 //
/////////////////////////////////////
assertValueInField(instance, TestUtils.TABLE_NAME_PERSON_MEMORY, 2, "testScriptId", null);
////////////////////////////////////
// add another script to record 1 //
////////////////////////////////////
storeAssociatedScriptInput.setRecordPrimaryKey(1);
storeAssociatedScriptInput.setCode("var i = 3;");
storeAssociatedScriptInput.setCommitMessage("Other field");
storeAssociatedScriptInput.setFieldName("otherScriptId");
new StoreAssociatedScriptAction().run(storeAssociatedScriptInput, storeAssociatedScriptOutput);
assertValueInField(instance, TestUtils.TABLE_NAME_PERSON_MEMORY, 1, "testScriptId", 1);
assertValueInField(instance, TestUtils.TABLE_NAME_PERSON_MEMORY, 1, "otherScriptId", 3);
assertValueInField(instance, "script", 3, "currentScriptRevisionId", 4);
}
/*******************************************************************************
**
*******************************************************************************/
private void assertValueInField(QInstance instance, String tableName, Serializable recordId, String fieldName, Serializable value) throws QException
{
GetInput getInput = new GetInput(instance);
getInput.setSession(new QSession());
getInput.setTableName(tableName);
getInput.setPrimaryKey(recordId);
GetOutput getOutput = new GetAction().execute(getInput);
if(getOutput.getRecord() == null)
{
fail("Expected value [" + value + "] in field [" + fieldName + "], record [" + tableName + "][" + recordId + "], but the record wasn't found...");
}
Serializable actual = getOutput.getRecord().getValue(fieldName);
assertEquals(value, actual, "Expected value in field [" + fieldName + "], record [" + tableName + "][" + recordId + "]");
}
}

View File

@ -0,0 +1,52 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/*******************************************************************************
** Unit test for TestScriptAction
*******************************************************************************/
class TestScriptActionTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
@Disabled("Not yet done.")
void test() throws QException
{
QInstance instance = TestUtils.defineInstance();
TestScriptInput input = new TestScriptInput(instance);
TestScriptOutput output = new TestScriptOutput();
new TestScriptAction().run(input, output);
}
}

View File

@ -0,0 +1,85 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts.logging;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
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.scripts.ScriptsMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.session.QSession;
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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
/*******************************************************************************
** Unit test for BuildScriptLogAndScriptLogLineExecutionLogger
*******************************************************************************/
class BuildScriptLogAndScriptLogLineExecutionLoggerTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
QInstance instance = TestUtils.defineInstance();
new ScriptsMetaDataProvider().defineAll(instance, TestUtils.MEMORY_BACKEND_NAME, null);
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(instance);
executeCodeInput.setSession(new QSession());
executeCodeInput.setInput(Map.of("a", 1));
BuildScriptLogAndScriptLogLineExecutionLogger logger = new BuildScriptLogAndScriptLogLineExecutionLogger(9999, 8888);
logger.acceptExecutionStart(executeCodeInput);
logger.acceptLogLine("This is a log");
logger.acceptLogLine("This is also a log");
logger.acceptExecutionEnd(true);
QRecord scriptLog = logger.getScriptLog();
assertNull(scriptLog.getValueInteger("id"));
assertNotNull(scriptLog.getValue("startTimestamp"));
assertNotNull(scriptLog.getValue("endTimestamp"));
assertNotNull(scriptLog.getValue("runTimeMillis"));
assertEquals(9999, scriptLog.getValueInteger("scriptId"));
assertEquals(8888, scriptLog.getValueInteger("scriptRevisionId"));
assertEquals("{a=1}", scriptLog.getValueString("input"));
assertEquals("true", scriptLog.getValueString("output"));
assertNull(scriptLog.getValueString("exception"));
assertFalse(scriptLog.getValueBoolean("hadError"));
List<QRecord> scriptLogLineRecords = logger.getScriptLogLines();
assertEquals(2, scriptLogLineRecords.size());
QRecord scriptLogLine = scriptLogLineRecords.get(0);
assertNull(scriptLogLine.getValueInteger("scriptLogId"));
assertNotNull(scriptLogLine.getValue("timestamp"));
assertEquals("This is a log", scriptLogLine.getValueString("text"));
scriptLogLine = scriptLogLineRecords.get(1);
assertEquals("This is also a log", scriptLogLine.getValueString("text"));
}
}

View File

@ -0,0 +1,103 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts.logging;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
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.scripts.ScriptsMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.session.QSession;
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;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
/*******************************************************************************
** Unit test for StoreScriptLogAndScriptLogLineExecutionLogger
*******************************************************************************/
class StoreScriptLogAndScriptLogLineExecutionLoggerTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
@AfterEach
void beforeAndAfterEach()
{
MemoryRecordStore.getInstance().reset();
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
QInstance instance = TestUtils.defineInstance();
new ScriptsMetaDataProvider().defineAll(instance, TestUtils.MEMORY_BACKEND_NAME, null);
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(instance);
executeCodeInput.setSession(new QSession());
executeCodeInput.setInput(Map.of("a", 1));
StoreScriptLogAndScriptLogLineExecutionLogger logger = new StoreScriptLogAndScriptLogLineExecutionLogger(9999, 8888);
logger.acceptExecutionStart(executeCodeInput);
logger.acceptLogLine("This is a log");
logger.acceptLogLine("This is also a log");
logger.acceptExecutionEnd(true);
List<QRecord> scriptLogRecords = TestUtils.queryTable(instance, "scriptLog");
assertEquals(1, scriptLogRecords.size());
QRecord scriptLog = scriptLogRecords.get(0);
assertNotNull(scriptLog.getValueInteger("id"));
assertNotNull(scriptLog.getValue("startTimestamp"));
assertNotNull(scriptLog.getValue("endTimestamp"));
assertNotNull(scriptLog.getValue("runTimeMillis"));
assertEquals(9999, scriptLog.getValueInteger("scriptId"));
assertEquals(8888, scriptLog.getValueInteger("scriptRevisionId"));
assertEquals("{a=1}", scriptLog.getValueString("input"));
assertEquals("true", scriptLog.getValueString("output"));
assertNull(scriptLog.getValueString("exception"));
assertFalse(scriptLog.getValueBoolean("hadError"));
List<QRecord> scriptLogLineRecords = TestUtils.queryTable(instance, "scriptLogLine");
assertEquals(2, scriptLogLineRecords.size());
QRecord scriptLogLine = scriptLogLineRecords.get(0);
assertEquals(scriptLog.getValueInteger("id"), scriptLogLine.getValueInteger("scriptLogId"));
assertNotNull(scriptLogLine.getValue("timestamp"));
assertEquals("This is a log", scriptLogLine.getValueString("text"));
scriptLogLine = scriptLogLineRecords.get(1);
assertEquals("This is also a log", scriptLogLine.getValueString("text"));
}
}

View File

@ -0,0 +1,58 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.tables;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/*******************************************************************************
** Unit test for GetAction
**
*******************************************************************************/
class GetActionTest
{
/*******************************************************************************
** At the core level, there isn't much that can be asserted, as it uses the
** mock implementation - just confirming that all of the "wiring" works.
**
*******************************************************************************/
@Test
public void test() throws QException
{
GetInput request = new GetInput(TestUtils.defineInstance());
request.setSession(TestUtils.getMockSession());
request.setTableName("person");
request.setPrimaryKey(1);
request.setShouldGenerateDisplayValues(true);
request.setShouldTranslatePossibleValues(true);
GetOutput result = new GetAction().execute(request);
assertNotNull(result);
assertNotNull(result.getRecord());
}
}

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