Initial version of scripts, javascript

This commit is contained in:
2022-10-31 11:35:48 -05:00
parent 0ada444fd4
commit 165583cd98
57 changed files with 7409 additions and 376 deletions

View File

@ -0,0 +1,94 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
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;
/*******************************************************************************
**
*******************************************************************************/
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();
Serializable codeOutput = qCodeExecutor.execute(codeReference, input.getContext(), 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,43 @@
/*
* 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;
/*******************************************************************************
**
*******************************************************************************/
public interface QCodeExecutor
{
/*******************************************************************************
**
*******************************************************************************/
Serializable execute(QCodeReference codeReference, Map<String, Serializable> context, 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;
/*******************************************************************************
**
*******************************************************************************/
public class QJavaExecutor implements QCodeExecutor
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public Serializable execute(QCodeReference codeReference, Map<String, Serializable> input, QCodeExecutionLoggerInterface executionLogger) throws QCodeException
{
Map<String, Object> context = new HashMap<>(input);
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,113 @@
/*
* 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.scripts.logging.HeaderAndDetailTableCodeExecutionLogger;
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.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
{
Serializable scriptId = getScriptId(input);
Script script = getScript(input, scriptId);
ScriptRevision scriptRevision = getCurrentScriptRevision(input, script.getCurrentScriptRevisionId());
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(input.getInstance());
executeCodeInput.setSession(input.getSession());
executeCodeInput.setContext(new HashMap<>(input.getInputValues()));
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT));
executeCodeInput.setExecutionLogger(new HeaderAndDetailTableCodeExecutionLogger(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);
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);
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);
return (getOutput.getRecord().getValue(input.getCodeReference().getFieldName()));
}
}

View File

@ -0,0 +1,206 @@
/*
* 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.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;
/*******************************************************************************
**
*******************************************************************************/
public class StoreAssociatedScriptAction
{
/*******************************************************************************
**
*******************************************************************************/
public void run(StoreAssociatedScriptInput input, StoreAssociatedScriptOutput output) throws QException
{
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());
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());
GetOutput getOutput = new GetAction().execute(getInput);
QRecord scriptType = getOutput.getRecord();
/////////////////////////
// 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,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 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.tables.QTableMetaData;
/*******************************************************************************
**
*******************************************************************************/
public class TestScriptAction
{
/*******************************************************************************
**
*******************************************************************************/
public void run(TestScriptInput input, TestScriptOutput output) throws QException
{
QTableMetaData table = input.getTable();
}
}

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.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.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Implementation of a code execution logger that logs into a header and child
** table
*******************************************************************************/
public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLoggerInterface
{
private static final Logger LOG = LogManager.getLogger(HeaderAndDetailTableCodeExecutionLogger.class);
private QRecord header;
private List<QRecord> children = new ArrayList<>();
private ExecuteCodeInput executeCodeInput;
private Serializable scriptId;
private Serializable scriptRevisionId;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public HeaderAndDetailTableCodeExecutionLogger(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.getContext().toString())));
}
/*******************************************************************************
**
*******************************************************************************/
protected QRecord buildDetailLogRecord(String logLine)
{
return (new QRecord()
.withValue("scriptLogId", header.getValue("id"))
.withValue("timestamp", Instant.now())
.withValue("text", truncate(logLine)));
}
/*******************************************************************************
**
*******************************************************************************/
private String truncate(String logLine)
{
return StringUtils.safeTruncate(logLine, 1000, "...");
}
/*******************************************************************************
**
*******************************************************************************/
private void updateHeaderAtEnd(Serializable output, Exception exception)
{
Instant startTimestamp = (Instant) header.getValue("startTimestamp");
Instant endTimestamp = Instant.now();
header.setValue("endTimestamp", endTimestamp);
header.setValue("runTimeMillis", startTimestamp.until(endTimestamp, ChronoUnit.MILLIS));
if(exception != null)
{
header.setValue("hadError", true);
header.setValue("error", exception.getMessage());
}
else
{
header.setValue("hadError", false);
header.setValue("output", truncate(String.valueOf(output)));
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptExecutionStart(ExecuteCodeInput executeCodeInput)
{
try
{
this.executeCodeInput = executeCodeInput;
QRecord scriptLog = buildHeaderRecord(executeCodeInput);
InsertInput insertInput = new InsertInput(executeCodeInput.getInstance());
insertInput.setSession(executeCodeInput.getSession());
insertInput.setTableName("scriptLog");
insertInput.setRecords(List.of(scriptLog));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
this.header = insertOutput.getRecords().get(0);
}
catch(Exception e)
{
LOG.warn("Error starting storage of script log", e);
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void acceptLogLine(String logLine)
{
children.add(buildDetailLogRecord(logLine));
}
/*******************************************************************************
**
*******************************************************************************/
@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(header));
new UpdateAction().execute(updateInput);
if(CollectionUtils.nullSafeHasContents(children))
{
InsertInput insertInput = new InsertInput(executeCodeInput.getInstance());
insertInput.setSession(executeCodeInput.getSession());
insertInput.setTableName("scriptLogLine");
insertInput.setRecords(children);
new InsertAction().execute(insertInput);
}
}
catch(Exception e)
{
LOG.warn("Error storing script log", e);
}
}
}

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 just 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 contextString = StringUtils.safeTruncate(ValueUtils.getValueAsString(executeCodeInput.getContext()), 250, "...");
LOG.info("Starting script execution: " + qCodeReference.getName() + ", uuid: " + uuid + ", with context: " + contextString);
}
/*******************************************************************************
**
*******************************************************************************/
@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,63 @@
/*
* 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;
/*******************************************************************************
**
*******************************************************************************/
public interface QCodeExecutionLoggerInterface
{
/*******************************************************************************
**
*******************************************************************************/
void acceptExecutionStart(ExecuteCodeInput executeCodeInput);
/*******************************************************************************
**
*******************************************************************************/
void acceptLogLine(String logLine);
/*******************************************************************************
**
*******************************************************************************/
default void log(String message)
{
acceptLogLine(message);
}
/*******************************************************************************
**
*******************************************************************************/
void acceptException(Exception exception);
/*******************************************************************************
**
*******************************************************************************/
void acceptExecutionEnd(Serializable output);
}

View File

@ -0,0 +1,77 @@
/*
* 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 generating reports
*
*******************************************************************************/
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

@ -608,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

@ -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.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> 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 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,117 @@
/*
* 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.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, String> inputValues;
/*******************************************************************************
**
*******************************************************************************/
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, String> getInputValues()
{
return inputValues;
}
/*******************************************************************************
** Setter for inputValues
**
*******************************************************************************/
public void setInputValues(Map<String, String> inputValues)
{
this.inputValues = inputValues;
}
/*******************************************************************************
** Fluent setter for inputValues
**
*******************************************************************************/
public RunAssociatedScriptInput withInputValues(Map<String, String> inputValues)
{
this.inputValues = inputValues;
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,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

@ -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

@ -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

@ -78,7 +78,8 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
private List<QFieldSection> sections;
private List<String> widgets;
private List<String> widgets;
private List<AssociatedScript> associatedScripts;
@ -754,6 +755,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
**

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()
private Integer scriptTypeId;
@QField()
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,481 @@
/*
* 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 ScriptLog extends QRecordEntity
{
public static final String TABLE_NAME = "scriptLog";
@QField()
private Integer id;
@QField()
private Instant createDate;
@QField()
private Instant modifyDate;
@QField()
private Integer scriptId;
@QField()
private Integer scriptRevisionId;
@QField()
private Instant startTimestamp;
@QField()
private Instant endTimestamp;
@QField()
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()
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()
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,150 @@
/*
* 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.tables.QTableMetaData;
/*******************************************************************************
**
*******************************************************************************/
public class ScriptsMetaDataProvider
{
/*******************************************************************************
**
*******************************************************************************/
public void defineStandardScriptsTables(QInstance instance, String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
{
for(QTableMetaData tableMetaData : defineStandardScriptsTables(backendName, backendDetailEnricher))
{
instance.addTable(tableMetaData);
}
}
/*******************************************************************************
**
*******************************************************************************/
public 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));
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData defineScriptTypeTable(String backendName) throws QException
{
return (defineStandardTable(backendName, ScriptType.TABLE_NAME, ScriptType.class));
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData defineScriptRevisionTable(String backendName) throws QException
{
return (defineStandardTable(backendName, ScriptRevision.TABLE_NAME, ScriptRevision.class)
.withRecordLabelFields(List.of("id")));
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData defineScriptLogTable(String backendName) throws QException
{
return (defineStandardTable(backendName, ScriptLog.TABLE_NAME, ScriptLog.class)
.withRecordLabelFields(List.of("id")));
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData defineScriptLogLineTable(String backendName) throws QException
{
return (defineStandardTable(backendName, ScriptLogLine.TABLE_NAME, ScriptLogLine.class)
.withRecordLabelFields(List.of("id")));
}
}

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

@ -49,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.

View File

@ -0,0 +1,67 @@
/*
* 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;
/*******************************************************************************
**
*******************************************************************************/
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,471 @@
/*
* 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;
/*******************************************************************************
**
*******************************************************************************/
public class BackendQueryFilterUtils
{
/*******************************************************************************
**
*******************************************************************************/
@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);
}
/*******************************************************************************
**
*******************************************************************************/
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);
});
}
/*******************************************************************************
**
*******************************************************************************/
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,226 @@
/*
* 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;
/*******************************************************************************
**
*******************************************************************************/
public class GeneralProcessUtils
{
/*******************************************************************************
**
*******************************************************************************/
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;
}
/*******************************************************************************
**
*******************************************************************************/
public static ListingHash<Serializable, QRecord> getForeignRecordListingHashMap(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) 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(foreignTablePrimaryKeyName, QCriteriaOperator.IN, foreignIds)));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
for(QRecord foreignRecord : queryOutput.getRecords())
{
foreignRecordMap.add(foreignRecord.getValue(foreignTablePrimaryKeyName), foreignRecord);
}
return foreignRecordMap;
}
/*******************************************************************************
**
*******************************************************************************/
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);
}
}
/*******************************************************************************
**
*******************************************************************************/
public static void addForeignRecordsListToRecordList(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException
{
ListingHash<Serializable, QRecord> foreignRecordMap = getForeignRecordListingHashMap(parentActionInput, sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTablePrimaryKeyName);
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));
}
}
}
}
/*******************************************************************************
**
*******************************************************************************/
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());
}
/*******************************************************************************
**
*******************************************************************************/
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());
}
/*******************************************************************************
**
*******************************************************************************/
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());
}
/*******************************************************************************
** Note - null values from the key field are NOT put in the map.
*******************************************************************************/
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);
}
/*******************************************************************************
** Note - null values from the key field are NOT put in the map.
*******************************************************************************/
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);
}
}