mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-20 14:10:44 +00:00
Compare commits
181 Commits
version-0.
...
wip/qqq-ta
Author | SHA1 | Date | |
---|---|---|---|
ecffda0e18 | |||
acb311ee52 | |||
4a53c0d42a | |||
fa5c4f715f | |||
7a56d1ae22 | |||
1d309afbea | |||
02fc031e09 | |||
e57292f10f | |||
b4a63e6e1b | |||
0d78555a05 | |||
6a1db1c533 | |||
fabde303ab | |||
6d173d5485 | |||
79ac48b7f9 | |||
f7c8513845 | |||
be30422c18 | |||
53c005051e | |||
3879d5412c | |||
d596346c44 | |||
ac88def08c | |||
29bb7252e8 | |||
f0bd6b4b80 | |||
726075f041 | |||
67a1afdc1a | |||
c832028961 | |||
774309e846 | |||
a19a516fc0 | |||
e153d3a7b4 | |||
34a1755e44 | |||
b4a2ba9582 | |||
9bb6600a9d | |||
4f081e7c79 | |||
a0a43d48f5 | |||
7c4e06abcc | |||
39d714fbb1 | |||
6975069049 | |||
f05759ae83 | |||
81e4d5d36d | |||
bff8a0f78a | |||
87fbbc797a | |||
742caba8d2 | |||
f6f5a07d3a | |||
bc50c1e22e | |||
71672d46ee | |||
75c84cd0ff | |||
0ff98ce7ea | |||
c53f5e935d | |||
f5f2cc5007 | |||
d62a4c6daf | |||
367fa4f657 | |||
5a5c9a0072 | |||
2db1adc9ab | |||
3d2708da23 | |||
6ec838c48b | |||
ebb7e7ab45 | |||
8c3648920d | |||
6d6510c223 | |||
2422d09c31 | |||
c04ab42bd9 | |||
c003d448d6 | |||
de8d668ea2 | |||
953d97c554 | |||
086787a5ca | |||
e6816174c3 | |||
ed60ad2a96 | |||
a943628e84 | |||
5cfcb420d0 | |||
593c9f25f9 | |||
ca560c933d | |||
51dd0b6b29 | |||
b924fdcffa | |||
5b84df1752 | |||
9af1fed422 | |||
4299199947 | |||
6f578eb2f0 | |||
be5b8f0869 | |||
c27723e956 | |||
cbff44f6a3 | |||
9167f356e9 | |||
dccefb0a40 | |||
e24f15e2d8 | |||
d0a0f93933 | |||
76d226b5e8 | |||
2bf611b24f | |||
56b9738f15 | |||
40d073bf54 | |||
c0297dea91 | |||
9e4743e8d8 | |||
976f173c93 | |||
3f74594c69 | |||
da08382055 | |||
22a9e4b06b | |||
c086874e64 | |||
75ae848afd | |||
53b74fb61d | |||
3ae938ac6e | |||
905ac6d72a | |||
a6af75ebdc | |||
2039c727b5 | |||
c38b8ac595 | |||
3187706967 | |||
ffdb392b9f | |||
f79940d4c3 | |||
be14afc11c | |||
360bf56481 | |||
b4507ba431 | |||
57675528b5 | |||
3fae35a2bf | |||
688d104635 | |||
d533e59a84 | |||
184ef8db47 | |||
a62a1f10cd | |||
a056c4618c | |||
ed22ab5917 | |||
598e26d9a1 | |||
b53d1823df | |||
6bc543fff7 | |||
e28f8b8317 | |||
9d1266036c | |||
b93032aae4 | |||
300af89687 | |||
059dffb620 | |||
1822dd8189 | |||
b75fd29a57 | |||
5ff91739d4 | |||
ada95431c1 | |||
c24b7cd84b | |||
bf80eb1845 | |||
db0c490a32 | |||
a73bd40c6f | |||
7eea0c08bb | |||
18c11d3869 | |||
a367ec717c | |||
65e8f7a71f | |||
900484c01c | |||
6bfe0cd3ea | |||
12d1de7135 | |||
e5efe8a64c | |||
9d3cd50c7b | |||
5ae18c31f9 | |||
ad11bf64db | |||
3a69957b43 | |||
7af5ad2655 | |||
57569e4c84 | |||
3791c069c7 | |||
0f799339d6 | |||
3e113a12b3 | |||
79304adcb0 | |||
2c192a3fd9 | |||
3772cf725f | |||
1cf83fb441 | |||
4efb818bfe | |||
f30b2a9ef8 | |||
2efc732530 | |||
1ed51e0a35 | |||
54bf5bed8f | |||
12eb3be3cb | |||
4105f034aa | |||
a38d57c7af | |||
14fa7fdb74 | |||
c07c007bc2 | |||
9fe5067374 | |||
599aff3487 | |||
59b7e0529c | |||
d9a98c5987 | |||
b58f93e627 | |||
1c1a0f99e8 | |||
d273d091df | |||
d0194d9580 | |||
54a0d6720f | |||
19ee5bcb23 | |||
eee7354e77 | |||
6b590324be | |||
a340299c67 | |||
6a01754479 | |||
4ccc726f2e | |||
eb151f0610 | |||
a18d2afee5 | |||
0007909792 | |||
78ba2b591c | |||
0b525f8775 |
@ -10,7 +10,7 @@ The bundle contains all of the sub-jars. It is named:
|
||||
|
||||
```qqq-${version}.jar```
|
||||
|
||||
You can also use fine grained jars:
|
||||
You can also use fine-grained jars:
|
||||
- `qqq-backend-core`: The core module. Useful if you're developing other modules.
|
||||
- `qqq-backend-module-rdbms`: Backend module for working with Relational Databases.
|
||||
- `qqq-backend-module-filesystem`: Backend module for working with Filesystems (including AWS S3).
|
||||
@ -35,4 +35,3 @@ 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/>.
|
||||
|
||||
|
2
pom.xml
2
pom.xml
@ -44,7 +44,7 @@
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<revision>0.14.0</revision>
|
||||
<revision>0.18.0-SNAPSHOT</revision>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
@ -39,6 +39,7 @@ import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
|
||||
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
|
||||
import com.kingsrook.qqq.backend.core.state.StateType;
|
||||
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
@ -50,6 +51,7 @@ public class AsyncJobManager
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(AsyncJobManager.class);
|
||||
|
||||
private String forcedJobUUID = null;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -69,7 +71,8 @@ public class AsyncJobManager
|
||||
*******************************************************************************/
|
||||
public <T extends Serializable> T startJob(String jobName, long timeout, TimeUnit timeUnit, AsyncJob<T> asyncJob) throws JobGoingAsyncException, QException
|
||||
{
|
||||
UUIDAndTypeStateKey uuidAndTypeStateKey = new UUIDAndTypeStateKey(UUID.randomUUID(), StateType.ASYNC_JOB_STATUS);
|
||||
UUID jobUUID = StringUtils.hasContent(forcedJobUUID) ? UUID.fromString(forcedJobUUID) : UUID.randomUUID();
|
||||
UUIDAndTypeStateKey uuidAndTypeStateKey = new UUIDAndTypeStateKey(jobUUID, StateType.ASYNC_JOB_STATUS);
|
||||
AsyncJobStatus asyncJobStatus = new AsyncJobStatus();
|
||||
asyncJobStatus.setState(AsyncJobState.RUNNING);
|
||||
getStateProvider().put(uuidAndTypeStateKey, asyncJobStatus);
|
||||
@ -205,4 +208,35 @@ public class AsyncJobManager
|
||||
jobStatus.ifPresent(asyncJobStatus -> asyncJobStatus.setCancelRequested(true));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for forcedJobUUID
|
||||
*******************************************************************************/
|
||||
public String getForcedJobUUID()
|
||||
{
|
||||
return (this.forcedJobUUID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for forcedJobUUID
|
||||
*******************************************************************************/
|
||||
public void setForcedJobUUID(String forcedJobUUID)
|
||||
{
|
||||
this.forcedJobUUID = forcedJobUUID;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for forcedJobUUID
|
||||
*******************************************************************************/
|
||||
public AsyncJobManager withForcedJobUUID(String forcedJobUUID)
|
||||
{
|
||||
this.forcedJobUUID = forcedJobUUID;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.async;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.BufferedRecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
@ -142,6 +143,11 @@ public class AsyncRecordPipeLoop
|
||||
jobState = asyncJobStatus.getState();
|
||||
}
|
||||
|
||||
if(recordPipe instanceof BufferedRecordPipe bufferedRecordPipe)
|
||||
{
|
||||
bufferedRecordPipe.finalFlush();
|
||||
}
|
||||
|
||||
LOG.debug("Job [" + jobUUID + "][" + jobName + "] completed with status: " + asyncJobStatus);
|
||||
|
||||
///////////////////////////////////
|
||||
|
@ -46,6 +46,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTableAccessor;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||
|
||||
@ -76,6 +77,21 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Execute to insert 1 audit, with a list of detail child records provided as just string messages
|
||||
*******************************************************************************/
|
||||
public static void executeWithStringDetails(String tableName, Integer recordId, Map<String, Serializable> securityKeyValues, String message, List<String> detailMessages)
|
||||
{
|
||||
List<QRecord> detailRecords = null;
|
||||
if(CollectionUtils.nullSafeHasContents(detailMessages))
|
||||
{
|
||||
detailRecords = detailMessages.stream().map(m -> new QRecord().withValue("message", m)).toList();
|
||||
}
|
||||
execute(tableName, recordId, securityKeyValues, message, detailRecords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Execute to insert 1 audit, with a list of detail child records
|
||||
*******************************************************************************/
|
||||
@ -162,7 +178,6 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
||||
////////////////////////////////////////////////
|
||||
// map names to ids and handle default values //
|
||||
////////////////////////////////////////////////
|
||||
Integer auditTableId = getIdForName("auditTable", auditSingleInput.getAuditTableName());
|
||||
Integer auditUserId = getIdForName("auditUser", Objects.requireNonNullElse(auditSingleInput.getAuditUserName(), getSessionUserName()));
|
||||
Instant timestamp = Objects.requireNonNullElse(auditSingleInput.getTimestamp(), Instant.now());
|
||||
|
||||
@ -170,7 +185,7 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
||||
// build record //
|
||||
//////////////////
|
||||
QRecord record = new QRecord()
|
||||
.withValue("auditTableId", auditTableId)
|
||||
.withValue("tableId", QQQTableAccessor.getTableId(auditSingleInput.getAuditTableName()))
|
||||
.withValue("auditUserId", auditUserId)
|
||||
.withValue("timestamp", timestamp)
|
||||
.withValue("message", auditSingleInput.getMessage())
|
||||
@ -272,15 +287,6 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
||||
insertInput.setTableName(tableName);
|
||||
QRecord record = new QRecord().withValue("name", nameValue);
|
||||
|
||||
if(tableName.equals("auditTable"))
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(nameValue);
|
||||
if(table != null)
|
||||
{
|
||||
record.setValue("label", table.getLabel());
|
||||
}
|
||||
}
|
||||
|
||||
insertInput.setRecords(List.of(record));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
id = insertOutput.getRecords().get(0).getValueInteger("id");
|
||||
|
@ -194,7 +194,7 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
continue;
|
||||
}
|
||||
|
||||
if(field.getType().equals(QFieldType.BLOB))
|
||||
if(field.getType().equals(QFieldType.BLOB) || field.getType().needsMasked())
|
||||
{
|
||||
detailRecord = new QRecord().withValue("message", "Set " + field.getLabel());
|
||||
}
|
||||
@ -209,7 +209,7 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
{
|
||||
if(!Objects.equals(oldValue, value))
|
||||
{
|
||||
if(field.getType().equals(QFieldType.BLOB))
|
||||
if(field.getType().equals(QFieldType.BLOB) || field.getType().needsMasked())
|
||||
{
|
||||
if(oldValue == null)
|
||||
{
|
||||
|
@ -45,6 +45,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAut
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTableAccessor;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
|
||||
@ -171,7 +172,7 @@ public class RecordAutomationStatusUpdater
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(TableTrigger.TABLE_NAME);
|
||||
countInput.setFilter(new QQueryFilter(
|
||||
new QFilterCriteria("tableName", QCriteriaOperator.EQUALS, table.getName()),
|
||||
new QFilterCriteria("tableId", QCriteriaOperator.EQUALS, QQQTableAccessor.getTableId(table.getName())),
|
||||
new QFilterCriteria(triggerEvent.equals(TriggerEvent.POST_INSERT) ? "postInsert" : "postUpdate", QCriteriaOperator.EQUALS, true)
|
||||
));
|
||||
CountOutput countOutput = new CountAction().execute(countInput);
|
||||
|
@ -42,6 +42,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
@ -74,7 +75,7 @@ public class RunRecordScriptAutomationHandler extends RecordAutomationHandler
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(ScriptRevision.TABLE_NAME);
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("scriptId", QCriteriaOperator.EQUALS, scriptId)));
|
||||
queryInput.withQueryJoin(new QueryJoin(Script.TABLE_NAME).withBaseTableOrAlias(ScriptRevision.TABLE_NAME).withJoinMetaData(QContext.getQInstance().getJoin("currentScriptRevision")));
|
||||
queryInput.withQueryJoin(new QueryJoin(Script.TABLE_NAME).withBaseTableOrAlias(ScriptRevision.TABLE_NAME).withJoinMetaData(QContext.getQInstance().getJoin(ScriptsMetaDataProvider.CURRENT_SCRIPT_REVISION_JOIN_NAME)));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
if(CollectionUtils.nullSafeIsEmpty(queryOutput.getRecords()))
|
||||
{
|
||||
|
@ -39,12 +39,15 @@ import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
@ -61,11 +64,15 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.Automatio
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
||||
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTableAccessor;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -264,22 +271,45 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(TableTrigger.TABLE_NAME);
|
||||
queryInput.setFilter(new QQueryFilter(
|
||||
new QFilterCriteria("tableName", QCriteriaOperator.EQUALS, table.getName()),
|
||||
new QFilterCriteria("tableId", QCriteriaOperator.EQUALS, QQQTableAccessor.getTableId(table.getName())),
|
||||
new QFilterCriteria(triggerEvent.equals(TriggerEvent.POST_INSERT) ? "postInsert" : "postUpdate", QCriteriaOperator.EQUALS, true)
|
||||
));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
// todo - get filter if there is/was one
|
||||
rs.add(new TableAutomationAction()
|
||||
.withName("Script:" + record.getValue("scriptId"))
|
||||
.withFilter(null)
|
||||
.withTriggerEvent(triggerEvent)
|
||||
.withPriority(record.getValueInteger("priority"))
|
||||
.withCodeReference(new QCodeReference(RunRecordScriptAutomationHandler.class))
|
||||
.withValues(MapBuilder.of("scriptId", record.getValue("scriptId")))
|
||||
.withIncludeRecordAssociations(true)
|
||||
);
|
||||
TableTrigger tableTrigger = new TableTrigger(record);
|
||||
|
||||
try
|
||||
{
|
||||
QQueryFilter filter = null;
|
||||
Integer filterId = tableTrigger.getFilterId();
|
||||
if(filterId != null)
|
||||
{
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(SavedFilter.TABLE_NAME);
|
||||
getInput.setPrimaryKey(filterId);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
if(getOutput.getRecord() != null)
|
||||
{
|
||||
SavedFilter savedFilter = new SavedFilter(getOutput.getRecord());
|
||||
filter = JsonUtils.toObject(savedFilter.getFilterJson(), QQueryFilter.class);
|
||||
}
|
||||
}
|
||||
|
||||
rs.add(new TableAutomationAction()
|
||||
.withName("Script:" + tableTrigger.getScriptId())
|
||||
.withFilter(filter)
|
||||
.withTriggerEvent(triggerEvent)
|
||||
.withPriority(tableTrigger.getPriority())
|
||||
.withCodeReference(new QCodeReference(RunRecordScriptAutomationHandler.class))
|
||||
.withValues(MapBuilder.of("scriptId", tableTrigger.getScriptId()))
|
||||
.withIncludeRecordAssociations(true)
|
||||
);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error setting up table trigger", e, logPair("tableTriggerId", tableTrigger.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ 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.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||
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;
|
||||
@ -42,18 +43,16 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
/*******************************************************************************
|
||||
** Standard/re-usable post-insert customizer, for the use case where, when we
|
||||
** do an insert into table "parent", we want a record automatically inserted into
|
||||
** table "child", and there's a foreign key in "parent", pointed at "child"
|
||||
** e.g., named: "parent.childId".
|
||||
** table "child". Optionally (based on RelationshipType), there can be a foreign
|
||||
** key in "parent", pointed at "child". e.g., named: "parent.childId".
|
||||
**
|
||||
** A similar use-case would have the foreign key in the child table - in which case,
|
||||
** we could add a "Type" enum, plus abstract method to get our "Type", then logic
|
||||
** to switch behavior based on type. See existing type enum, but w/ only 1 case :)
|
||||
*******************************************************************************/
|
||||
public abstract class ChildInserterPostInsertCustomizer extends AbstractPostInsertCustomizer
|
||||
{
|
||||
public enum RelationshipType
|
||||
{
|
||||
PARENT_POINTS_AT_CHILD
|
||||
PARENT_POINTS_AT_CHILD,
|
||||
CHILD_POINTS_AT_PARENT
|
||||
}
|
||||
|
||||
|
||||
@ -68,10 +67,17 @@ public abstract class ChildInserterPostInsertCustomizer extends AbstractPostInse
|
||||
*******************************************************************************/
|
||||
public abstract String getChildTableName();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract String getForeignKeyFieldName();
|
||||
public String getForeignKeyFieldName()
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
@ -88,7 +94,7 @@ public abstract class ChildInserterPostInsertCustomizer extends AbstractPostInse
|
||||
{
|
||||
try
|
||||
{
|
||||
List<QRecord> rs = new ArrayList<>();
|
||||
List<QRecord> rs = records;
|
||||
List<QRecord> childrenToInsert = new ArrayList<>();
|
||||
QTableMetaData table = getInsertInput().getTable();
|
||||
QTableMetaData childTable = getInsertInput().getInstance().getTable(getChildTableName());
|
||||
@ -97,12 +103,37 @@ public abstract class ChildInserterPostInsertCustomizer extends AbstractPostInse
|
||||
// iterate over the inserted records, building a list child records to insert //
|
||||
// for ones missing a value in the foreign key field. //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
for(QRecord record : records)
|
||||
switch(getRelationshipType())
|
||||
{
|
||||
if(record.getValue(getForeignKeyFieldName()) == null)
|
||||
case PARENT_POINTS_AT_CHILD ->
|
||||
{
|
||||
childrenToInsert.add(buildChildForRecord(record));
|
||||
String foreignKeyFieldName = getForeignKeyFieldName();
|
||||
try
|
||||
{
|
||||
table.getField(foreignKeyFieldName);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QRuntimeException("For RelationshipType.PARENT_POINTS_AT_CHILD, a valid foreignKeyFieldName in the parent table must be given. "
|
||||
+ "[" + foreignKeyFieldName + "] is not a valid field name in table [" + table.getName() + "]");
|
||||
}
|
||||
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(record.getValue(foreignKeyFieldName) == null)
|
||||
{
|
||||
childrenToInsert.add(buildChildForRecord(record));
|
||||
}
|
||||
}
|
||||
}
|
||||
case CHILD_POINTS_AT_PARENT ->
|
||||
{
|
||||
for(QRecord record : records)
|
||||
{
|
||||
childrenToInsert.add(buildChildForRecord(record));
|
||||
}
|
||||
}
|
||||
default -> throw new IllegalStateException("Unexpected value: " + getRelationshipType());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
@ -129,51 +160,70 @@ public abstract class ChildInserterPostInsertCustomizer extends AbstractPostInse
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// for the PARENT_POINTS_AT_CHILD relationship type:
|
||||
// iterate over the original list of records again - for any that need a child (e.g., are missing //
|
||||
// foreign key), set their foreign key to a newly inserted child's key, and add them to be updated. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> recordsToUpdate = new ArrayList<>();
|
||||
for(QRecord record : records)
|
||||
switch(getRelationshipType())
|
||||
{
|
||||
Serializable primaryKey = record.getValue(table.getPrimaryKeyField());
|
||||
if(record.getValue(getForeignKeyFieldName()) == null)
|
||||
case PARENT_POINTS_AT_CHILD ->
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// get the corresponding child record, if it has any errors, set that as a warning in the parent //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QRecord childRecord = insertedRecordIterator.next();
|
||||
if(CollectionUtils.nullSafeHasContents(childRecord.getErrors()))
|
||||
rs = new ArrayList<>();
|
||||
List<QRecord> recordsToUpdate = new ArrayList<>();
|
||||
for(QRecord record : records)
|
||||
{
|
||||
for(QStatusMessage error : childRecord.getErrors())
|
||||
Serializable primaryKey = record.getValue(table.getPrimaryKeyField());
|
||||
if(record.getValue(getForeignKeyFieldName()) == null)
|
||||
{
|
||||
record.addWarning(new QWarningMessage("Error creating child " + childTable.getLabel() + " (" + error.toString() + ")"));
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// get the corresponding child record, if it has any errors, set that as a warning in the parent //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QRecord childRecord = insertedRecordIterator.next();
|
||||
if(CollectionUtils.nullSafeHasContents(childRecord.getErrors()))
|
||||
{
|
||||
for(QStatusMessage error : childRecord.getErrors())
|
||||
{
|
||||
record.addWarning(new QWarningMessage("Error creating child " + childTable.getLabel() + " (" + error.toString() + ")"));
|
||||
}
|
||||
rs.add(record);
|
||||
continue;
|
||||
}
|
||||
|
||||
Serializable foreignKey = childRecord.getValue(childTable.getPrimaryKeyField());
|
||||
recordsToUpdate.add(new QRecord().withValue(table.getPrimaryKeyField(), primaryKey).withValue(getForeignKeyFieldName(), foreignKey));
|
||||
record.setValue(getForeignKeyFieldName(), foreignKey);
|
||||
rs.add(record);
|
||||
}
|
||||
else
|
||||
{
|
||||
rs.add(record);
|
||||
}
|
||||
rs.add(record);
|
||||
continue;
|
||||
}
|
||||
|
||||
Serializable foreignKey = childRecord.getValue(childTable.getPrimaryKeyField());
|
||||
recordsToUpdate.add(new QRecord().withValue(table.getPrimaryKeyField(), primaryKey).withValue(getForeignKeyFieldName(), foreignKey));
|
||||
record.setValue(getForeignKeyFieldName(), foreignKey);
|
||||
rs.add(record);
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// update the originally inserted records to reference their new children //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(getInsertInput().getTableName());
|
||||
updateInput.setRecords(recordsToUpdate);
|
||||
updateInput.setTransaction(this.insertInput.getTransaction());
|
||||
new UpdateAction().execute(updateInput);
|
||||
}
|
||||
else
|
||||
case CHILD_POINTS_AT_PARENT ->
|
||||
{
|
||||
rs.add(record);
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - some version of looking at the inserted children to confirm that they were inserted, and updating the parents with warnings if they weren't //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
default -> throw new IllegalStateException("Unexpected value: " + getRelationshipType());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// update the originally inserted records to reference their new children //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(getInsertInput().getTableName());
|
||||
updateInput.setRecords(recordsToUpdate);
|
||||
updateInput.setTransaction(this.insertInput.getTransaction());
|
||||
new UpdateAction().execute(updateInput);
|
||||
|
||||
return (rs);
|
||||
}
|
||||
catch(RuntimeException re)
|
||||
{
|
||||
throw (re);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new RuntimeException("Error inserting new child records for new parent records", e);
|
||||
|
@ -31,7 +31,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOu
|
||||
** Interface for the Aggregate action.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface AggregateInterface
|
||||
public interface AggregateInterface extends BaseQueryInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.interfaces;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import com.kingsrook.qqq.backend.core.model.querystats.QueryStat;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base class for "query" (e.g., read-operations) action interfaces (query, count, aggregate).
|
||||
** Initially just here for the QueryStat methods - if we expand those to apply
|
||||
** to insert/update/delete, well, then rename this maybe to BaseActionInterface?
|
||||
*******************************************************************************/
|
||||
public interface BaseQueryInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default void setQueryStat(QueryStat queryStat)
|
||||
{
|
||||
//////////
|
||||
// noop //
|
||||
//////////
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default QueryStat getQueryStat()
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default void setQueryStatFirstResultTime()
|
||||
{
|
||||
QueryStat queryStat = getQueryStat();
|
||||
if(queryStat != null)
|
||||
{
|
||||
if(queryStat.getFirstResultTimestamp() == null)
|
||||
{
|
||||
queryStat.setFirstResultTimestamp(Instant.now());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default void cancelAction()
|
||||
{
|
||||
//////////////////////////////////////////////
|
||||
// initially at least, a noop in base class //
|
||||
//////////////////////////////////////////////
|
||||
}
|
||||
|
||||
}
|
@ -31,7 +31,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
** Interface for the Count action.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface CountInterface
|
||||
public interface CountInterface extends BaseQueryInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -31,10 +31,11 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
** Interface for the Query action.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface QueryInterface
|
||||
public interface QueryInterface extends BaseQueryInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
QueryOutput execute(QueryInput queryInput) throws QException;
|
||||
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.NoCodeWidgetRend
|
||||
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.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
|
||||
@ -50,6 +51,7 @@ 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.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.NoCodeWidgetFrontendComponentMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
@ -57,6 +59,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponen
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
|
||||
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
|
||||
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
|
||||
@ -483,6 +486,35 @@ public class RunProcessAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected String determineBasepullKeyValue(QProcessMetaData process, BasepullConfiguration basepullConfiguration) throws QException
|
||||
{
|
||||
String basepullKeyValue = (basepullConfiguration.getKeyValue() != null) ? basepullConfiguration.getKeyValue() : process.getName();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if backend specifies that it uses variants, look for that data in the session and append to our basepull key //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(process.getSchedule() != null && process.getSchedule().getVariantBackend() != null)
|
||||
{
|
||||
QSession session = QContext.getQSession();
|
||||
QBackendMetaData backendMetaData = QContext.getQInstance().getBackend(process.getSchedule().getVariantBackend());
|
||||
if(session.getBackendVariants() == null || !session.getBackendVariants().containsKey(backendMetaData.getVariantOptionsTableTypeValue()))
|
||||
{
|
||||
LOG.info("Could not find Backend Variant information for Backend '" + backendMetaData.getName() + "'");
|
||||
}
|
||||
else
|
||||
{
|
||||
basepullKeyValue += "-" + session.getBackendVariants().get(backendMetaData.getVariantOptionsTableTypeValue());
|
||||
}
|
||||
}
|
||||
|
||||
return (basepullKeyValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Insert or update the last runtime value for this basepull into the backend.
|
||||
*******************************************************************************/
|
||||
@ -491,7 +523,7 @@ public class RunProcessAction
|
||||
String basepullTableName = basepullConfiguration.getTableName();
|
||||
String basepullKeyFieldName = basepullConfiguration.getKeyField();
|
||||
String basepullLastRunTimeFieldName = basepullConfiguration.getLastRunTimeFieldName();
|
||||
String basepullKeyValue = (basepullConfiguration.getKeyValue() != null) ? basepullConfiguration.getKeyValue() : process.getName();
|
||||
String basepullKeyValue = determineBasepullKeyValue(process, basepullConfiguration);
|
||||
|
||||
///////////////////////////////////////
|
||||
// get the stored basepull timestamp //
|
||||
@ -571,7 +603,7 @@ public class RunProcessAction
|
||||
String basepullKeyFieldName = basepullConfiguration.getKeyField();
|
||||
String basepullLastRunTimeFieldName = basepullConfiguration.getLastRunTimeFieldName();
|
||||
Integer basepullHoursBackForInitialTimestamp = basepullConfiguration.getHoursBackForInitialTimestamp();
|
||||
String basepullKeyValue = (basepullConfiguration.getKeyValue() != null) ? basepullConfiguration.getKeyValue() : process.getName();
|
||||
String basepullKeyValue = determineBasepullKeyValue(process, basepullConfiguration);
|
||||
|
||||
///////////////////////////////////////
|
||||
// get the stored basepull timestamp //
|
||||
|
@ -23,22 +23,33 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
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.actions.scripts.logging.ScriptExecutionLoggerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QCodeException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.AbstractRunScriptInput;
|
||||
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.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.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevisionFile;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
@ -101,6 +112,17 @@ public class ExecuteCodeAction
|
||||
context.putAll(input.getInput());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// set the qCodeExecutor into any context objects which are QCodeExecutorAware //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
for(Serializable value : context.values())
|
||||
{
|
||||
if(value instanceof QCodeExecutorAware qCodeExecutorAware)
|
||||
{
|
||||
qCodeExecutorAware.setQCodeExecutor(qCodeExecutor);
|
||||
}
|
||||
}
|
||||
|
||||
Serializable codeOutput = qCodeExecutor.execute(codeReference, context, executionLogger);
|
||||
output.setOutput(codeOutput);
|
||||
executionLogger.acceptExecutionEnd(codeOutput);
|
||||
@ -122,7 +144,17 @@ public class ExecuteCodeAction
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static ExecuteCodeInput setupExecuteCodeInput(AbstractRunScriptInput<?> input, ScriptRevision scriptRevision)
|
||||
public static ExecuteCodeInput setupExecuteCodeInput(AbstractRunScriptInput<?> input, ScriptRevision scriptRevision) throws QException
|
||||
{
|
||||
return setupExecuteCodeInput(input, scriptRevision, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static ExecuteCodeInput setupExecuteCodeInput(AbstractRunScriptInput<?> input, ScriptRevision scriptRevision, String fileName) throws QException
|
||||
{
|
||||
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput();
|
||||
executeCodeInput.setInput(new HashMap<>(Objects.requireNonNullElseGet(input.getInputValues(), HashMap::new)));
|
||||
@ -139,7 +171,49 @@ public class ExecuteCodeAction
|
||||
context.put("scriptUtils", input.getScriptUtils());
|
||||
}
|
||||
|
||||
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
|
||||
if(CollectionUtils.nullSafeIsEmpty(scriptRevision.getFiles()))
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(ScriptRevisionFile.TABLE_NAME);
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("scriptRevisionId", QCriteriaOperator.EQUALS, scriptRevision.getId())));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
scriptRevision.setFiles(new ArrayList<>());
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
scriptRevision.getFiles().add(new ScriptRevisionFile(record));
|
||||
}
|
||||
}
|
||||
|
||||
List<ScriptRevisionFile> files = scriptRevision.getFiles();
|
||||
if(files == null || files.isEmpty())
|
||||
{
|
||||
throw (new QException("Script Revision " + scriptRevision.getId() + " had more than 1 associated ScriptRevisionFile (and the name to use was not specified)."));
|
||||
}
|
||||
else
|
||||
{
|
||||
String contents = null;
|
||||
if(fileName == null || files.size() == 1)
|
||||
{
|
||||
contents = files.get(0).getContents();
|
||||
}
|
||||
else
|
||||
{
|
||||
for(ScriptRevisionFile file : files)
|
||||
{
|
||||
if(file.getFileName().equals(fileName))
|
||||
{
|
||||
contents = file.getContents();
|
||||
}
|
||||
}
|
||||
if(contents == null)
|
||||
{
|
||||
throw (new QException("Could not find file named " + fileName + " for Script Revision " + scriptRevision.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(contents).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
|
||||
}
|
||||
|
||||
ExecuteCodeAction.addApiUtilityToContext(context, scriptRevision);
|
||||
context.put("qqq", new QqqScriptUtils());
|
||||
@ -157,7 +231,19 @@ public class ExecuteCodeAction
|
||||
*******************************************************************************/
|
||||
public static void addApiUtilityToContext(Map<String, Serializable> context, ScriptRevision scriptRevision)
|
||||
{
|
||||
if(!StringUtils.hasContent(scriptRevision.getApiName()) || !StringUtils.hasContent(scriptRevision.getApiVersion()))
|
||||
addApiUtilityToContext(context, scriptRevision.getApiName(), scriptRevision.getApiVersion());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Try to (dynamically) load the ApiScriptUtils object from the api middleware
|
||||
** module -- in case the runtime doesn't have that module deployed (e.g, not in
|
||||
** the project pom).
|
||||
*******************************************************************************/
|
||||
public static void addApiUtilityToContext(Map<String, Serializable> context, String apiName, String apiVersion)
|
||||
{
|
||||
if(!StringUtils.hasContent(apiName) || !StringUtils.hasContent(apiVersion))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -165,7 +251,7 @@ public class ExecuteCodeAction
|
||||
try
|
||||
{
|
||||
Class<?> apiScriptUtilsClass = Class.forName("com.kingsrook.qqq.api.utils.ApiScriptUtils");
|
||||
Object apiScriptUtilsObject = apiScriptUtilsClass.getConstructor(String.class, String.class).newInstance(scriptRevision.getApiName(), scriptRevision.getApiVersion());
|
||||
Object apiScriptUtilsObject = apiScriptUtilsClass.getConstructor(String.class, String.class).newInstance(apiName, apiVersion);
|
||||
context.put("api", (Serializable) apiScriptUtilsObject);
|
||||
}
|
||||
catch(ClassNotFoundException e)
|
||||
|
@ -41,4 +41,24 @@ public interface QCodeExecutor
|
||||
*******************************************************************************/
|
||||
Serializable execute(QCodeReference codeReference, Map<String, Serializable> inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException;
|
||||
|
||||
/*******************************************************************************
|
||||
** Process an object from the script's language/runtime into a (more) native java object.
|
||||
** e.g., a Nashorn ScriptObjectMirror will end up as a "primitive", or a List or Map of such
|
||||
**
|
||||
*******************************************************************************/
|
||||
default Object convertObjectToJava(Object object) throws QCodeException
|
||||
{
|
||||
return (object);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Convert a native java object into one for the script's language/runtime.
|
||||
** e.g., a java Instant to a Nashorn Date
|
||||
**
|
||||
*******************************************************************************/
|
||||
default Object convertJavaObject(Object object, Object requestedTypeHint) throws QCodeException
|
||||
{
|
||||
return (object);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.scripts;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface for classes that can accept a QCodeExecutor object via a setter.
|
||||
*******************************************************************************/
|
||||
public interface QCodeExecutorAware
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void setQCodeExecutor(QCodeExecutor qCodeExecutor);
|
||||
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.scripts;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.BuildScriptLogAndScriptLogLineExecutionLogger;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAdHocRecordScriptInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAdHocRecordScriptOutput;
|
||||
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.actions.tables.get.GetInput;
|
||||
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.model.metadata.code.AdHocScriptCodeReference;
|
||||
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.model.scripts.Script;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTableAccessor;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class RecordScriptTestInterface implements TestScriptActionInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setupTestScriptInput(TestScriptInput testScriptInput, ExecuteCodeInput executeCodeInput) throws QException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void execute(TestScriptInput input, TestScriptOutput output) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
Serializable scriptId = input.getInputValues().get("scriptId");
|
||||
QRecord script = new GetAction().executeForRecord(new GetInput(Script.TABLE_NAME).withPrimaryKey(scriptId));
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// look up the records being tested against //
|
||||
//////////////////////////////////////////////
|
||||
String tableName = QQQTableAccessor.getTableName(script.getValueInteger("tableId"));
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
if(table == null)
|
||||
{
|
||||
throw (new QException("Could not find table [" + tableName + "] for script"));
|
||||
}
|
||||
|
||||
String recordPrimaryKeyList = ValueUtils.getValueAsString(input.getInputValues().get("recordPrimaryKeyList"));
|
||||
if(!StringUtils.hasContent(recordPrimaryKeyList))
|
||||
{
|
||||
throw (new QException("Record primary key list was not given."));
|
||||
}
|
||||
|
||||
QueryOutput queryOutput = new QueryAction().execute(new QueryInput(tableName)
|
||||
.withFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, recordPrimaryKeyList.split(","))))
|
||||
.withIncludeAssociations(true));
|
||||
if(CollectionUtils.nullSafeIsEmpty(queryOutput.getRecords()))
|
||||
{
|
||||
throw (new QException("No records were found by the given primary keys."));
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// set up & run the action //
|
||||
/////////////////////////////
|
||||
RunAdHocRecordScriptInput runAdHocRecordScriptInput = new RunAdHocRecordScriptInput();
|
||||
runAdHocRecordScriptInput.setRecordList(queryOutput.getRecords());
|
||||
|
||||
BuildScriptLogAndScriptLogLineExecutionLogger executionLogger = new BuildScriptLogAndScriptLogLineExecutionLogger(null, null);
|
||||
runAdHocRecordScriptInput.setLogger(executionLogger);
|
||||
|
||||
runAdHocRecordScriptInput.setTableName(tableName);
|
||||
runAdHocRecordScriptInput.setCodeReference((AdHocScriptCodeReference) input.getCodeReference());
|
||||
RunAdHocRecordScriptOutput runAdHocRecordScriptOutput = new RunAdHocRecordScriptOutput();
|
||||
new RunAdHocRecordScriptAction().run(runAdHocRecordScriptInput, runAdHocRecordScriptOutput);
|
||||
|
||||
/////////////////////////////////
|
||||
// send outputs back to caller //
|
||||
/////////////////////////////////
|
||||
output.setScriptLog(executionLogger.getScriptLog());
|
||||
output.setScriptLogLines(executionLogger.getScriptLogLines());
|
||||
if(runAdHocRecordScriptOutput.getException().isPresent())
|
||||
{
|
||||
output.setException(runAdHocRecordScriptOutput.getException().get());
|
||||
}
|
||||
}
|
||||
catch(QException e)
|
||||
{
|
||||
output.setException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QFieldMetaData> getTestInputFields()
|
||||
{
|
||||
return (List.of(new QFieldMetaData("recordPrimaryKeyList", QFieldType.STRING).withLabel("Record Primary Key List")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QFieldMetaData> getTestOutputFields()
|
||||
{
|
||||
return (Collections.emptyList());
|
||||
}
|
||||
|
||||
}
|
@ -51,6 +51,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReferen
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
@ -197,7 +198,7 @@ public class RunAdHocRecordScriptAction
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(ScriptRevision.TABLE_NAME);
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("script.id", QCriteriaOperator.EQUALS, codeReference.getScriptId())));
|
||||
queryInput.withQueryJoin(new QueryJoin(Script.TABLE_NAME).withBaseTableOrAlias(ScriptRevision.TABLE_NAME).withJoinMetaData(QContext.getQInstance().getJoin("currentScriptRevision")));
|
||||
queryInput.withQueryJoin(new QueryJoin(Script.TABLE_NAME).withBaseTableOrAlias(ScriptRevision.TABLE_NAME).withJoinMetaData(QContext.getQInstance().getJoin(ScriptsMetaDataProvider.CURRENT_SCRIPT_REVISION_JOIN_NAME)));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
|
||||
|
@ -110,6 +110,7 @@ public class RunAssociatedScriptAction
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName("scriptRevision");
|
||||
getInput.setPrimaryKey(scriptRevisionId);
|
||||
getInput.setIncludeAssociations(true);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
if(getOutput.getRecord() == null)
|
||||
{
|
||||
|
@ -31,6 +31,8 @@ import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
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;
|
||||
@ -47,6 +49,7 @@ 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.processes.implementations.scripts.StoreScriptRevisionProcessStep;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
@ -180,41 +183,19 @@ public class StoreAssociatedScriptAction
|
||||
}
|
||||
}
|
||||
|
||||
QRecord scriptRevision = new QRecord()
|
||||
.withValue("scriptId", script.getValue("id"))
|
||||
.withValue("contents", input.getCode())
|
||||
.withValue("apiName", input.getApiName())
|
||||
.withValue("apiVersion", input.getApiVersion())
|
||||
.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();
|
||||
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();
|
||||
updateInput.setTableName("script");
|
||||
updateInput.setRecords(List.of(script));
|
||||
new UpdateAction().execute(updateInput);
|
||||
RunBackendStepInput storeScriptRevisionInput = new RunBackendStepInput();
|
||||
storeScriptRevisionInput.addValue("scriptId", script.getValue("id"));
|
||||
storeScriptRevisionInput.addValue("commitMessage", commitMessage);
|
||||
storeScriptRevisionInput.addValue("apiName", input.getApiName());
|
||||
storeScriptRevisionInput.addValue("apiVersion", input.getApiVersion());
|
||||
storeScriptRevisionInput.addValue("fileNames", "script");
|
||||
storeScriptRevisionInput.addValue("fileContents:script", input.getCode());
|
||||
RunBackendStepOutput storeScriptRevisionOutput = new RunBackendStepOutput();
|
||||
new StoreScriptRevisionProcessStep().run(storeScriptRevisionInput, storeScriptRevisionOutput);
|
||||
|
||||
output.setScriptId(script.getValueInteger("id"));
|
||||
output.setScriptName(script.getValueString("name"));
|
||||
output.setScriptRevisionId(scriptRevision.getValueInteger("id"));
|
||||
output.setScriptRevisionSequenceNo(scriptRevision.getValueInteger("sequenceNo"));
|
||||
output.setScriptRevisionId(storeScriptRevisionOutput.getValueInteger("scriptRevisionId"));
|
||||
output.setScriptRevisionSequenceNo(storeScriptRevisionOutput.getValueInteger("scriptRevisionSequenceNo"));
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ public class Log4jCodeExecutionLogger implements QCodeExecutionLoggerInterface
|
||||
private QCodeReference qCodeReference;
|
||||
private String uuid = UUID.randomUUID().toString();
|
||||
|
||||
private boolean includeUUID = true;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -52,7 +53,7 @@ public class Log4jCodeExecutionLogger implements QCodeExecutionLoggerInterface
|
||||
this.qCodeReference = executeCodeInput.getCodeReference();
|
||||
|
||||
String inputString = StringUtils.safeTruncate(ValueUtils.getValueAsString(executeCodeInput.getInput()), 250, "...");
|
||||
LOG.info("Starting script execution: " + qCodeReference.getName() + ", uuid: " + uuid + ", with input: " + inputString);
|
||||
LOG.info("Starting script execution: " + qCodeReference.getName() + (includeUUID ? ", uuid: " + uuid : "") + ", with input: " + inputString);
|
||||
}
|
||||
|
||||
|
||||
@ -63,7 +64,7 @@ public class Log4jCodeExecutionLogger implements QCodeExecutionLoggerInterface
|
||||
@Override
|
||||
public void acceptLogLine(String logLine)
|
||||
{
|
||||
LOG.info("Script log: " + uuid + ": " + logLine);
|
||||
LOG.info("Script log: " + (includeUUID ? uuid + ": " : "") + logLine);
|
||||
}
|
||||
|
||||
|
||||
@ -74,7 +75,7 @@ public class Log4jCodeExecutionLogger implements QCodeExecutionLoggerInterface
|
||||
@Override
|
||||
public void acceptException(Exception exception)
|
||||
{
|
||||
LOG.info("Script Exception: " + uuid, exception);
|
||||
LOG.info("Script Exception: " + (includeUUID ? uuid : ""), exception);
|
||||
}
|
||||
|
||||
|
||||
@ -86,7 +87,38 @@ public class Log4jCodeExecutionLogger implements QCodeExecutionLoggerInterface
|
||||
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);
|
||||
LOG.info("Finished script execution: " + qCodeReference.getName() + (includeUUID ? ", uuid: " + uuid : "") + ", with output: " + outputString);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for includeUUID
|
||||
*******************************************************************************/
|
||||
public boolean getIncludeUUID()
|
||||
{
|
||||
return (this.includeUUID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for includeUUID
|
||||
*******************************************************************************/
|
||||
public void setIncludeUUID(boolean includeUUID)
|
||||
{
|
||||
this.includeUUID = includeUUID;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for includeUUID
|
||||
*******************************************************************************/
|
||||
public Log4jCodeExecutionLogger withIncludeUUID(boolean includeUUID)
|
||||
{
|
||||
this.includeUUID = includeUUID;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
|
||||
/*******************************************************************************
|
||||
** Interface to provide logging functionality to QCodeExecution (e.g., scripts)
|
||||
*******************************************************************************/
|
||||
public interface QCodeExecutionLoggerInterface
|
||||
public interface QCodeExecutionLoggerInterface extends Serializable
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Implementation of a code execution logger that logs to System.out and
|
||||
** System.err (for exceptions)
|
||||
*******************************************************************************/
|
||||
public class SystemOutExecutionLogger implements QCodeExecutionLoggerInterface
|
||||
{
|
||||
private QCodeReference qCodeReference;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void acceptExecutionStart(ExecuteCodeInput executeCodeInput)
|
||||
{
|
||||
this.qCodeReference = executeCodeInput.getCodeReference();
|
||||
|
||||
String inputString = ValueUtils.getValueAsString(executeCodeInput.getInput());
|
||||
System.out.println("Starting script execution: " + qCodeReference.getName() + ", with input: " + inputString);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void acceptLogLine(String logLine)
|
||||
{
|
||||
System.out.println("Script log: " + logLine);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void acceptException(Exception exception)
|
||||
{
|
||||
System.out.println("Script Exception: " + exception.getMessage());
|
||||
exception.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void acceptExecutionEnd(Serializable output)
|
||||
{
|
||||
String outputString = ValueUtils.getValueAsString(output);
|
||||
System.out.println("Finished script execution: " + qCodeReference.getName() + ", with output: " + outputString);
|
||||
}
|
||||
|
||||
}
|
@ -23,9 +23,15 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.AggregateInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.querystats.QueryStat;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
|
||||
@ -36,6 +42,12 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
*******************************************************************************/
|
||||
public class AggregateAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(AggregateAction.class);
|
||||
|
||||
private AggregateInterface aggregateInterface;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -43,11 +55,36 @@ public class AggregateAction
|
||||
{
|
||||
ActionHelper.validateSession(aggregateInput);
|
||||
|
||||
QTableMetaData table = aggregateInput.getTable();
|
||||
QBackendMetaData backend = aggregateInput.getBackend();
|
||||
|
||||
QueryStat queryStat = QueryStatManager.newQueryStat(backend, table, aggregateInput.getFilter());
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(aggregateInput.getBackend());
|
||||
// todo pre-customization - just get to modify the request?
|
||||
AggregateOutput aggregateOutput = qModule.getAggregateInterface().execute(aggregateInput);
|
||||
// todo post-customization - can do whatever w/ the result if you want
|
||||
|
||||
aggregateInterface = qModule.getAggregateInterface();
|
||||
aggregateInterface.setQueryStat(queryStat);
|
||||
AggregateOutput aggregateOutput = aggregateInterface.execute(aggregateInput);
|
||||
|
||||
QueryStatManager.getInstance().add(queryStat);
|
||||
|
||||
return aggregateOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void cancel()
|
||||
{
|
||||
if(aggregateInterface == null)
|
||||
{
|
||||
LOG.warn("aggregateInterface object was null when requested to cancel");
|
||||
return;
|
||||
}
|
||||
|
||||
aggregateInterface.cancelAction();
|
||||
}
|
||||
}
|
||||
|
@ -23,9 +23,15 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.querystats.QueryStat;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
|
||||
@ -36,6 +42,12 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
*******************************************************************************/
|
||||
public class CountAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(CountAction.class);
|
||||
|
||||
private CountInterface countInterface;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -43,11 +55,36 @@ public class CountAction
|
||||
{
|
||||
ActionHelper.validateSession(countInput);
|
||||
|
||||
QTableMetaData table = countInput.getTable();
|
||||
QBackendMetaData backend = countInput.getBackend();
|
||||
|
||||
QueryStat queryStat = QueryStatManager.newQueryStat(backend, table, countInput.getFilter());
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(countInput.getBackend());
|
||||
// todo pre-customization - just get to modify the request?
|
||||
CountOutput countOutput = qModule.getCountInterface().execute(countInput);
|
||||
// todo post-customization - can do whatever w/ the result if you want
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(countInput.getBackend());
|
||||
|
||||
countInterface = qModule.getCountInterface();
|
||||
countInterface.setQueryStat(queryStat);
|
||||
CountOutput countOutput = countInterface.execute(countInput);
|
||||
|
||||
QueryStatManager.getInstance().add(queryStat);
|
||||
|
||||
return countOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void cancel()
|
||||
{
|
||||
if(countInterface == null)
|
||||
{
|
||||
LOG.warn("countInterface object was null when requested to cancel");
|
||||
return;
|
||||
}
|
||||
|
||||
countInterface.cancelAction();
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
@ -117,12 +118,14 @@ public class DeleteAction
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there's a query filter, but the interface doesn't support using a query filter, then do a query for the filter, to get a list of primary keys instead //
|
||||
// or - anytime there are associations on the table we want primary keys, as that's what the manage associations method uses //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(deleteInput.getQueryFilter() != null && !deleteInterface.supportsQueryFilterInput())
|
||||
if(deleteInput.getQueryFilter() != null && (!deleteInterface.supportsQueryFilterInput() || CollectionUtils.nullSafeHasContents(table.getAssociations())))
|
||||
{
|
||||
LOG.info("Querying for primary keys, for backend module " + qModule.getBackendType() + " which does not support queryFilter input for deletes");
|
||||
LOG.info("Querying for primary keys, for table " + table.getName() + " in backend module " + qModule.getBackendType() + " which does not support queryFilter input for deletes (or the table has associations)");
|
||||
List<Serializable> primaryKeyList = getPrimaryKeysFromQueryFilter(deleteInput);
|
||||
deleteInput.setPrimaryKeys(primaryKeyList);
|
||||
primaryKeys = primaryKeyList;
|
||||
|
||||
if(primaryKeyList.isEmpty())
|
||||
{
|
||||
@ -165,10 +168,22 @@ public class DeleteAction
|
||||
|
||||
if(!primaryKeysToRemoveFromInput.isEmpty())
|
||||
{
|
||||
primaryKeys.removeAll(primaryKeysToRemoveFromInput);
|
||||
if(primaryKeys == null)
|
||||
{
|
||||
LOG.warn("There were primary keys to remove from the input, but no primary key list (filter supplied as input?)", new LogPair("primaryKeysToRemoveFromInput", primaryKeysToRemoveFromInput));
|
||||
}
|
||||
else
|
||||
{
|
||||
primaryKeys.removeAll(primaryKeysToRemoveFromInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// stash a copy of primary keys that didn't have errors (for use in manageAssociations below) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Set<Serializable> primaryKeysWithoutErrors = new HashSet<>(CollectionUtils.nonNullList(primaryKeys));
|
||||
|
||||
////////////////////////////////////
|
||||
// have the backend do the delete //
|
||||
////////////////////////////////////
|
||||
@ -187,11 +202,13 @@ public class DeleteAction
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if a record had a validation warning, but then an execution error, remove it from the warning list - so it's only in one of them. //
|
||||
// also, always remove from
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QRecord outputRecordWithError : outputRecordsWithErrors)
|
||||
{
|
||||
Serializable pkey = outputRecordWithError.getValue(primaryKeyFieldName);
|
||||
recordsWithValidationWarnings.remove(pkey);
|
||||
primaryKeysWithoutErrors.remove(pkey);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -211,15 +228,23 @@ public class DeleteAction
|
||||
////////////////////////////////////////
|
||||
// delete associations, if applicable //
|
||||
////////////////////////////////////////
|
||||
manageAssociations(deleteInput);
|
||||
manageAssociations(primaryKeysWithoutErrors, deleteInput);
|
||||
|
||||
///////////////////////////////////
|
||||
// do the audit //
|
||||
// todo - add input.omitDmlAudit //
|
||||
///////////////////////////////////
|
||||
DMLAuditInput dmlAuditInput = new DMLAuditInput().withTableActionInput(deleteInput);
|
||||
oldRecordList.ifPresent(l -> dmlAuditInput.setRecordList(l));
|
||||
new DMLAuditAction().execute(dmlAuditInput);
|
||||
//////////////////
|
||||
// do the audit //
|
||||
//////////////////
|
||||
if(deleteInput.getOmitDmlAudit())
|
||||
{
|
||||
LOG.debug("Requested to omit DML audit");
|
||||
}
|
||||
else
|
||||
{
|
||||
DMLAuditInput dmlAuditInput = new DMLAuditInput()
|
||||
.withTableActionInput(deleteInput)
|
||||
.withAuditContext(deleteInput.getAuditContext());
|
||||
oldRecordList.ifPresent(l -> dmlAuditInput.setRecordList(l));
|
||||
new DMLAuditAction().execute(dmlAuditInput);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// finally, run the post-delete customizer, if there is one //
|
||||
@ -340,7 +365,7 @@ public class DeleteAction
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void manageAssociations(DeleteInput deleteInput) throws QException
|
||||
private void manageAssociations(Set<Serializable> primaryKeysWithoutErrors, DeleteInput deleteInput) throws QException
|
||||
{
|
||||
QTableMetaData table = deleteInput.getTable();
|
||||
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||
@ -353,7 +378,7 @@ public class DeleteAction
|
||||
|
||||
if(join.getJoinOns().size() == 1 && join.getJoinOns().get(0).getLeftField().equals(table.getPrimaryKeyField()))
|
||||
{
|
||||
filter.addCriteria(new QFilterCriteria(join.getJoinOns().get(0).getRightField(), QCriteriaOperator.IN, deleteInput.getPrimaryKeys()));
|
||||
filter.addCriteria(new QFilterCriteria(join.getJoinOns().get(0).getRightField(), QCriteriaOperator.IN, new ArrayList<>(primaryKeysWithoutErrors)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -23,8 +23,6 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@ -33,35 +31,24 @@ import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCusto
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.GetInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.GetActionCacheHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
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.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.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -70,8 +57,6 @@ import org.apache.commons.lang.NotImplementedException;
|
||||
*******************************************************************************/
|
||||
public class GetAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(InsertAction.class);
|
||||
|
||||
private Optional<AbstractPostQueryCustomizer> postGetRecordCustomizer;
|
||||
|
||||
private GetInput getInput;
|
||||
@ -79,6 +64,16 @@ public class GetAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QRecord executeForRecord(GetInput getInput) throws QException
|
||||
{
|
||||
return (execute(getInput).getRecord());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -125,36 +120,7 @@ public class GetAction
|
||||
////////////////////////////
|
||||
if(table.getCacheOf() != null)
|
||||
{
|
||||
if(getOutput.getRecord() == null)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// if the record wasn't found, see if we should look in cache-source //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
QRecord recordFromSource = tryToGetFromCacheSource(getInput);
|
||||
if(recordFromSource != null)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// good, we found a record from the source, make sure we should cache it, and if so, do it now //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QRecord recordToCache = mapSourceRecordToCacheRecord(table, recordFromSource);
|
||||
boolean shouldCacheRecord = shouldCacheRecord(table, recordToCache);
|
||||
if(shouldCacheRecord)
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(getInput.getTableName());
|
||||
insertInput.setRecords(List.of(recordToCache));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
getOutput.setRecord(insertOutput.getRecords().get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// if the record was found, but it's too old, maybe re-fetch from cache source //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
refreshCacheIfExpired(getInput, getOutput);
|
||||
}
|
||||
new GetActionCacheHelper().handleCaching(getInput, getOutput);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
@ -171,168 +137,12 @@ public class GetAction
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Run a GetAction by using the QueryAction instead (e.g., with a filter made
|
||||
** from the pkey/ukey, and returning the single record if found).
|
||||
*******************************************************************************/
|
||||
private boolean shouldCacheRecord(QTableMetaData table, QRecord recordToCache)
|
||||
public GetOutput executeViaQuery(GetInput getInput) throws QException
|
||||
{
|
||||
boolean shouldCacheRecord = true;
|
||||
recordMatchExclusionLoop:
|
||||
for(CacheUseCase useCase : CollectionUtils.nonNullList(table.getCacheOf().getUseCases()))
|
||||
{
|
||||
for(QQueryFilter filter : CollectionUtils.nonNullList(useCase.getExcludeRecordsMatching()))
|
||||
{
|
||||
if(BackendQueryFilterUtils.doesRecordMatch(filter, recordToCache))
|
||||
{
|
||||
LOG.info("Not caching record because it matches a use case's filter exclusion", new LogPair("record", recordToCache), new LogPair("filter", filter));
|
||||
shouldCacheRecord = false;
|
||||
break recordMatchExclusionLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (shouldCacheRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QRecord mapSourceRecordToCacheRecord(QTableMetaData table, QRecord recordFromSource)
|
||||
{
|
||||
QRecord cacheRecord = new QRecord(recordFromSource);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// make sure every value in the qRecord is set, because we will possibly be doing an update //
|
||||
// on this record and want to null out any fields not set, not leave them populated //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(String fieldName : table.getFields().keySet())
|
||||
{
|
||||
if(!cacheRecord.getValues().containsKey(fieldName))
|
||||
{
|
||||
cacheRecord.setValue(fieldName, null);
|
||||
}
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(table.getCacheOf().getCachedDateFieldName()))
|
||||
{
|
||||
cacheRecord.setValue(table.getCacheOf().getCachedDateFieldName(), Instant.now());
|
||||
}
|
||||
return (cacheRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void refreshCacheIfExpired(GetInput getInput, GetOutput getOutput) throws QException
|
||||
{
|
||||
QTableMetaData table = getInput.getTable();
|
||||
Integer expirationSeconds = table.getCacheOf().getExpirationSeconds();
|
||||
if(expirationSeconds != null)
|
||||
{
|
||||
QRecord cachedRecord = getOutput.getRecord();
|
||||
Instant cachedDate = cachedRecord.getValueInstant(table.getCacheOf().getCachedDateFieldName());
|
||||
if(cachedDate == null || cachedDate.isBefore(Instant.now().minus(expirationSeconds, ChronoUnit.SECONDS)))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// keep the serial key from the old record in case we need to delete it //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
Serializable oldRecordPrimaryKey = getOutput.getRecord().getValue(table.getPrimaryKeyField());
|
||||
boolean shouldDeleteCachedRecord = true;
|
||||
|
||||
///////////////////////////////////////////
|
||||
// fetch record from original source now //
|
||||
///////////////////////////////////////////
|
||||
QRecord recordFromSource = tryToGetFromCacheSource(getInput);
|
||||
if(recordFromSource != null)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// if the record was found in the source, put it into the output //
|
||||
// object so returned back to caller, check that it should actually //
|
||||
// be cached before doing so //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
QRecord recordToCache = mapSourceRecordToCacheRecord(table, recordFromSource);
|
||||
recordToCache.setValue(table.getPrimaryKeyField(), cachedRecord.getValue(table.getPrimaryKeyField()));
|
||||
getOutput.setRecord(recordToCache);
|
||||
|
||||
if(shouldCacheRecord(table, recordToCache))
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(getInput.getTableName());
|
||||
updateInput.setRecords(List.of(recordToCache));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
getOutput.setRecord(updateOutput.getRecords().get(0));
|
||||
shouldDeleteCachedRecord = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we did not get a record back from the source, empty out the getOutput's record //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
getOutput.setRecord(null);
|
||||
}
|
||||
|
||||
if(shouldDeleteCachedRecord)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// if the record is no longer in the source, then remove it from the cache //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(getInput.getTableName());
|
||||
deleteInput.setPrimaryKeys(List.of(oldRecordPrimaryKey));
|
||||
new DeleteAction().execute(deleteInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QRecord tryToGetFromCacheSource(GetInput getInput) throws QException
|
||||
{
|
||||
QRecord recordFromSource = null;
|
||||
QTableMetaData table = getInput.getTable();
|
||||
|
||||
for(CacheUseCase cacheUseCase : CollectionUtils.nonNullList(table.getCacheOf().getUseCases()))
|
||||
{
|
||||
if(CacheUseCase.Type.UNIQUE_KEY_TO_UNIQUE_KEY.equals(cacheUseCase.getType()) && getInput.getUniqueKey() != null)
|
||||
{
|
||||
recordFromSource = getFromCachedSourceForUniqueKeyToUniqueKey(getInput, table.getCacheOf().getSourceTable());
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo!!
|
||||
throw new NotImplementedException("Not-yet-implemented cache use case type: " + cacheUseCase.getType());
|
||||
}
|
||||
}
|
||||
|
||||
return (recordFromSource);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QRecord getFromCachedSourceForUniqueKeyToUniqueKey(GetInput getInput, String sourceTableName) throws QException
|
||||
{
|
||||
/////////////////////////////////////////////////////
|
||||
// do a Get on the source table, by the unique key //
|
||||
/////////////////////////////////////////////////////
|
||||
GetInput sourceGetInput = new GetInput();
|
||||
sourceGetInput.setTableName(sourceTableName);
|
||||
sourceGetInput.setUniqueKey(getInput.getUniqueKey());
|
||||
GetOutput sourceGetOutput = new GetAction().execute(sourceGetInput);
|
||||
QRecord outputRecord = sourceGetOutput.getRecord();
|
||||
|
||||
return (outputRecord);
|
||||
return (new DefaultGetInterface().execute(getInput));
|
||||
}
|
||||
|
||||
|
||||
@ -345,42 +155,7 @@ public class GetAction
|
||||
@Override
|
||||
public GetOutput execute(GetInput getInput) throws QException
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(getInput.getTableName());
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// build filter using either pkey or unique key //
|
||||
//////////////////////////////////////////////////
|
||||
QQueryFilter filter = new QQueryFilter();
|
||||
if(getInput.getPrimaryKey() != null)
|
||||
{
|
||||
filter.addCriteria(new QFilterCriteria(getInput.getTable().getPrimaryKeyField(), QCriteriaOperator.EQUALS, getInput.getPrimaryKey()));
|
||||
}
|
||||
else if(getInput.getUniqueKey() != null)
|
||||
{
|
||||
for(Map.Entry<String, Serializable> entry : getInput.getUniqueKey().entrySet())
|
||||
{
|
||||
if(entry.getValue() == null)
|
||||
{
|
||||
filter.addCriteria(new QFilterCriteria(entry.getKey(), QCriteriaOperator.IS_BLANK));
|
||||
}
|
||||
else
|
||||
{
|
||||
filter.addCriteria(new QFilterCriteria(entry.getKey(), QCriteriaOperator.EQUALS, entry.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QException("No primaryKey or uniqueKey was passed to Get"));
|
||||
}
|
||||
|
||||
queryInput.setFilter(filter);
|
||||
queryInput.setIncludeAssociations(getInput.getIncludeAssociations());
|
||||
queryInput.setAssociationNamesToInclude(getInput.getAssociationNamesToInclude());
|
||||
queryInput.setShouldFetchHeavyFields(getInput.getShouldFetchHeavyFields());
|
||||
queryInput.setShouldMaskPasswords(getInput.getShouldMaskPasswords());
|
||||
queryInput.setShouldOmitHiddenFields(getInput.getShouldOmitHiddenFields());
|
||||
QueryInput queryInput = convertGetInputToQueryInput(getInput);
|
||||
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
@ -395,6 +170,48 @@ public class GetAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QueryInput convertGetInputToQueryInput(GetInput getInput) throws QException
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(getInput.getTableName());
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// build filter using either pkey or unique key //
|
||||
//////////////////////////////////////////////////
|
||||
QQueryFilter filter = new QQueryFilter();
|
||||
if(getInput.getPrimaryKey() != null)
|
||||
{
|
||||
filter.addCriteria(new QFilterCriteria(getInput.getTable().getPrimaryKeyField(), QCriteriaOperator.EQUALS, getInput.getPrimaryKey()));
|
||||
}
|
||||
else if(getInput.getUniqueKey() != null)
|
||||
{
|
||||
for(Map.Entry<String, Serializable> entry : getInput.getUniqueKey().entrySet())
|
||||
{
|
||||
if(entry.getValue() == null)
|
||||
{
|
||||
filter.addCriteria(new QFilterCriteria(entry.getKey(), QCriteriaOperator.IS_BLANK));
|
||||
}
|
||||
else
|
||||
{
|
||||
filter.addCriteria(new QFilterCriteria(entry.getKey(), QCriteriaOperator.EQUALS, entry.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QException("No primaryKey or uniqueKey was passed to Get"));
|
||||
}
|
||||
|
||||
queryInput.setFilter(filter);
|
||||
queryInput.setCommonParamsFrom(getInput);
|
||||
return queryInput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Run the necessary actions on a record. This may include setting display values,
|
||||
** translating possible values, and running post-record customizations.
|
||||
|
@ -78,6 +78,28 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QRecord executeForRecord(InsertInput insertInput) throws QException
|
||||
{
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
return (insertOutput.getRecords().get(0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static List<QRecord> executeForRecords(InsertInput insertInput) throws QException
|
||||
{
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
return (insertOutput.getRecords());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -133,7 +155,10 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
}
|
||||
else
|
||||
{
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(insertInput).withRecordList(insertOutput.getRecords()));
|
||||
new DMLAuditAction().execute(new DMLAuditInput()
|
||||
.withTableActionInput(insertInput)
|
||||
.withAuditContext(insertInput.getAuditContext())
|
||||
.withRecordList(insertOutput.getRecords()));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
@ -34,8 +34,11 @@ import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.BufferedRecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipeBufferedWrapper;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryActionCacheHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
@ -47,12 +50,14 @@ 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.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.querystats.QueryStat;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
@ -71,6 +76,7 @@ public class QueryAction
|
||||
private Optional<AbstractPostQueryCustomizer> postQueryRecordCustomizer;
|
||||
|
||||
private QueryInput queryInput;
|
||||
private QueryInterface queryInterface;
|
||||
private QPossibleValueTranslator qPossibleValueTranslator;
|
||||
|
||||
|
||||
@ -87,12 +93,14 @@ public class QueryAction
|
||||
throw (new QException("Table name was not specified in query input"));
|
||||
}
|
||||
|
||||
if(queryInput.getTable() == null)
|
||||
QTableMetaData table = queryInput.getTable();
|
||||
if(table == null)
|
||||
{
|
||||
throw (new QException("A table named [" + queryInput.getTableName() + "] was not found in the active QInstance"));
|
||||
}
|
||||
|
||||
postQueryRecordCustomizer = QCodeLoader.getTableCustomizer(AbstractPostQueryCustomizer.class, queryInput.getTable(), TableCustomizers.POST_QUERY_RECORD.getRole());
|
||||
QBackendMetaData backend = queryInput.getBackend();
|
||||
postQueryRecordCustomizer = QCodeLoader.getTableCustomizer(AbstractPostQueryCustomizer.class, table, TableCustomizers.POST_QUERY_RECORD.getRole());
|
||||
this.queryInput = queryInput;
|
||||
|
||||
if(queryInput.getRecordPipe() != null)
|
||||
@ -109,11 +117,24 @@ public class QueryAction
|
||||
}
|
||||
}
|
||||
|
||||
QueryStat queryStat = QueryStatManager.newQueryStat(backend, table, queryInput.getFilter());
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(queryInput.getBackend());
|
||||
// todo pre-customization - just get to modify the request?
|
||||
QueryOutput queryOutput = qModule.getQueryInterface().execute(queryInput);
|
||||
// todo post-customization - can do whatever w/ the result if you want
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(backend);
|
||||
|
||||
queryInterface = qModule.getQueryInterface();
|
||||
queryInterface.setQueryStat(queryStat);
|
||||
QueryOutput queryOutput = queryInterface.execute(queryInput);
|
||||
|
||||
QueryStatManager.getInstance().add(queryStat);
|
||||
|
||||
////////////////////////////
|
||||
// handle cache use-cases //
|
||||
////////////////////////////
|
||||
if(table.getCacheOf() != null)
|
||||
{
|
||||
new QueryActionCacheHelper().handleCaching(queryInput, queryOutput);
|
||||
}
|
||||
|
||||
if(queryInput.getRecordPipe() instanceof BufferedRecordPipe bufferedRecordPipe)
|
||||
{
|
||||
@ -319,4 +340,20 @@ public class QueryAction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void cancel()
|
||||
{
|
||||
if(queryInterface == null)
|
||||
{
|
||||
LOG.warn("queryInterface object was null when requested to cancel");
|
||||
return;
|
||||
}
|
||||
|
||||
queryInterface.cancelAction();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
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.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.replace.ReplaceInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.replace.ReplaceOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Action to do a "replace" - e.g: Update rows with unique-key values that are
|
||||
** already in the table; insert rows whose unique keys weren't already in the
|
||||
** table, and delete rows that weren't in the input (all based on a
|
||||
** UniqueKey that's part of the input)
|
||||
**
|
||||
** Note - the filter in the ReplaceInput - its role is to limit what rows are
|
||||
** potentially deleted. e.g., if you have a table that's segmented, and you're
|
||||
** only replacing a particular segment of it (say, for 1 client), then you pass
|
||||
** in a filter that finds rows matching that segment. See Test for example.
|
||||
*******************************************************************************/
|
||||
public class ReplaceAction extends AbstractQActionFunction<ReplaceInput, ReplaceOutput>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public ReplaceOutput execute(ReplaceInput input) throws QException
|
||||
{
|
||||
ReplaceOutput output = new ReplaceOutput();
|
||||
|
||||
QBackendTransaction transaction = input.getTransaction();
|
||||
boolean weOwnTheTransaction = false;
|
||||
|
||||
try
|
||||
{
|
||||
QTableMetaData table = input.getTable();
|
||||
UniqueKey uniqueKey = input.getKey();
|
||||
String primaryKeyField = table.getPrimaryKeyField();
|
||||
if(transaction == null)
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(input.getTableName());
|
||||
transaction = new InsertAction().openTransaction(insertInput);
|
||||
weOwnTheTransaction = true;
|
||||
}
|
||||
|
||||
List<QRecord> insertList = new ArrayList<>();
|
||||
List<QRecord> updateList = new ArrayList<>();
|
||||
List<Serializable> primaryKeysToKeep = new ArrayList<>();
|
||||
|
||||
for(List<QRecord> page : CollectionUtils.getPages(input.getRecords(), 1000))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// originally it was thought that we'd need to pass the filter in here //
|
||||
// but, it's been decided not to. the filter only applies to what we can delete //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
Map<List<Serializable>, Serializable> existingKeys = UniqueKeyHelper.getExistingKeys(transaction, table, page, uniqueKey);
|
||||
for(QRecord record : page)
|
||||
{
|
||||
Optional<List<Serializable>> keyValues = UniqueKeyHelper.getKeyValues(table, uniqueKey, record);
|
||||
if(keyValues.isPresent())
|
||||
{
|
||||
if(existingKeys.containsKey(keyValues.get()))
|
||||
{
|
||||
Serializable primaryKey = existingKeys.get(keyValues.get());
|
||||
record.setValue(primaryKeyField, primaryKey);
|
||||
updateList.add(record);
|
||||
primaryKeysToKeep.add(primaryKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
insertList.add(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(table.getName());
|
||||
insertInput.setRecords(insertList);
|
||||
insertInput.setTransaction(transaction);
|
||||
insertInput.setOmitDmlAudit(input.getOmitDmlAudit());
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
primaryKeysToKeep.addAll(insertOutput.getRecords().stream().map(r -> r.getValue(primaryKeyField)).toList());
|
||||
output.setInsertOutput(insertOutput);
|
||||
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(table.getName());
|
||||
updateInput.setRecords(updateList);
|
||||
updateInput.setTransaction(transaction);
|
||||
updateInput.setOmitDmlAudit(input.getOmitDmlAudit());
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
output.setUpdateOutput(updateOutput);
|
||||
|
||||
QQueryFilter deleteFilter = new QQueryFilter(new QFilterCriteria(primaryKeyField, QCriteriaOperator.NOT_IN, primaryKeysToKeep));
|
||||
if(input.getFilter() != null)
|
||||
{
|
||||
deleteFilter.addSubFilter(input.getFilter());
|
||||
}
|
||||
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(table.getName());
|
||||
deleteInput.setQueryFilter(deleteFilter);
|
||||
deleteInput.setTransaction(transaction);
|
||||
deleteInput.setOmitDmlAudit(input.getOmitDmlAudit());
|
||||
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
|
||||
output.setDeleteOutput(deleteOutput);
|
||||
|
||||
if(weOwnTheTransaction)
|
||||
{
|
||||
transaction.commit();
|
||||
}
|
||||
|
||||
return (output);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
if(weOwnTheTransaction)
|
||||
{
|
||||
transaction.rollback();
|
||||
}
|
||||
throw (new QException("Error executing replace action", e));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(weOwnTheTransaction)
|
||||
{
|
||||
transaction.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -83,6 +83,28 @@ public class UpdateAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QRecord executeForRecord(UpdateInput updateInput) throws QException
|
||||
{
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
return (updateOutput.getRecords().get(0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static List<QRecord> executeForRecords(UpdateInput updateInput) throws QException
|
||||
{
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
return (updateOutput.getRecords());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -374,6 +396,11 @@ public class UpdateAction
|
||||
//////////////////////////////////////////////////////
|
||||
for(QRecord record : page)
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(record.getAssociatedRecords() != null && record.getAssociatedRecords().containsKey(association.getName()))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.tables.helpers;
|
||||
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For actions that may want to set a timeout, and cancel themselves if they run
|
||||
** too long - this class helps.
|
||||
**
|
||||
** Construct with the timeout (delay & timeUnit), and a runnable that takes care
|
||||
** of doing the cancel (e.g., cancelling a JDBC statement).
|
||||
**
|
||||
** Call start() to make a future get scheduled (note, if delay was null or <= 0,
|
||||
** then it doesn't get scheduled at all).
|
||||
**
|
||||
** Call cancel() if the action got far enough/completed, to cancel the future.
|
||||
**
|
||||
** You can check didTimeout (getDidTimeout()) to know if the timeout did occur.
|
||||
*******************************************************************************/
|
||||
public class ActionTimeoutHelper
|
||||
{
|
||||
private final Integer delay;
|
||||
private final TimeUnit timeUnit;
|
||||
private final Runnable runnable;
|
||||
private ScheduledFuture<?> future;
|
||||
|
||||
private boolean didTimeout = false;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ActionTimeoutHelper(Integer delay, TimeUnit timeUnit, Runnable runnable)
|
||||
{
|
||||
this.delay = delay;
|
||||
this.timeUnit = timeUnit;
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void start()
|
||||
{
|
||||
if(delay == null || delay <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
future = Executors.newSingleThreadScheduledExecutor().schedule(() ->
|
||||
{
|
||||
didTimeout = true;
|
||||
runnable.run();
|
||||
}, delay, timeUnit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void cancel()
|
||||
{
|
||||
if(future != null)
|
||||
{
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for didTimeout
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getDidTimeout()
|
||||
{
|
||||
return didTimeout;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.tables.helpers;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class CacheUtils
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(CacheUtils.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
static QRecord mapSourceRecordToCacheRecord(QTableMetaData table, QRecord recordFromSource, CacheUseCase cacheUseCase)
|
||||
{
|
||||
QRecord cacheRecord = new QRecord(recordFromSource);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// make sure every value in the qRecord is set, because we will possibly be doing an update //
|
||||
// on this record and want to null out any fields not set, not leave them populated //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(String fieldName : table.getFields().keySet())
|
||||
{
|
||||
if(fieldName.equals(table.getPrimaryKeyField()))
|
||||
{
|
||||
if(!cacheUseCase.getDoCopySourcePrimaryKeyToCache())
|
||||
{
|
||||
cacheRecord.removeValue(fieldName);
|
||||
}
|
||||
}
|
||||
else if(!cacheRecord.getValues().containsKey(fieldName))
|
||||
{
|
||||
cacheRecord.setValue(fieldName, null);
|
||||
}
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(table.getCacheOf().getCachedDateFieldName()))
|
||||
{
|
||||
cacheRecord.setValue(table.getCacheOf().getCachedDateFieldName(), Instant.now());
|
||||
}
|
||||
return (cacheRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
static boolean shouldCacheRecord(QTableMetaData table, QRecord recordToCache)
|
||||
{
|
||||
boolean shouldCacheRecord = true;
|
||||
recordMatchExclusionLoop:
|
||||
for(CacheUseCase useCase : CollectionUtils.nonNullList(table.getCacheOf().getUseCases()))
|
||||
{
|
||||
for(QQueryFilter filter : CollectionUtils.nonNullList(useCase.getExcludeRecordsMatching()))
|
||||
{
|
||||
if(BackendQueryFilterUtils.doesRecordMatch(filter, recordToCache))
|
||||
{
|
||||
LOG.info("Not caching record because it matches a use case's filter exclusion", new LogPair("record", recordToCache), new LogPair("filter", filter));
|
||||
shouldCacheRecord = false;
|
||||
break recordMatchExclusionLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (shouldCacheRecord);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,241 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.tables.helpers;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
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.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class GetActionCacheHelper
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(GetActionCacheHelper.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void handleCaching(GetInput getInput, GetOutput getOutput) throws QException
|
||||
{
|
||||
///////////////////////////////////////////////////////
|
||||
// copy Get input & output into Query input & output //
|
||||
///////////////////////////////////////////////////////
|
||||
QueryInput queryInput = GetAction.convertGetInputToQueryInput(getInput);
|
||||
|
||||
QueryOutput queryOutput = new QueryOutput(queryInput);
|
||||
if(getOutput.getRecord() != null)
|
||||
{
|
||||
queryOutput.addRecord(getOutput.getRecord());
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// run the QueryActionCacheHelper //
|
||||
////////////////////////////////////
|
||||
new QueryActionCacheHelper().handleCaching(queryInput, queryOutput);
|
||||
|
||||
///////////////////////////////////
|
||||
// set result back in get output //
|
||||
///////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
|
||||
{
|
||||
getOutput.setRecord(queryOutput.getRecords().get(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
getOutput.setRecord(null);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// In July 2023, initial caching was added in QueryAction. //
|
||||
// at this time, it felt wrong to essentially duplicate this code between Get & Query - as Get is a simplified use-case of Query. //
|
||||
// so - we'll keep this code here, as a potential quick/easy fallback - but - see above - where we use QueryActionCacheHelper instead. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
public void handleCaching(GetInput getInput, GetOutput getOutput) throws QException
|
||||
{
|
||||
if(getOutput.getRecord() == null)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// if the record wasn't found, see if we should look in cache-source //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
QRecord recordFromSource = tryToGetFromCacheSource(getInput);
|
||||
if(recordFromSource != null)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// good, we found a record from the source, make sure we should cache it, and if so, do it now //
|
||||
// note, we always return the record from the source, even if we don't cache it. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QTableMetaData table = getInput.getTable();
|
||||
QRecord recordToCache = CacheUtils.mapSourceRecordToCacheRecord(table, recordFromSource);
|
||||
getOutput.setRecord(recordToCache);
|
||||
|
||||
boolean shouldCacheRecord = CacheUtils.shouldCacheRecord(table, recordToCache);
|
||||
if(shouldCacheRecord)
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(getInput.getTableName());
|
||||
insertInput.setRecords(List.of(recordToCache));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// update the result record from the insert (e.g., so we get its id, just in case we care) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
getOutput.setRecord(insertOutput.getRecords().get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// if the record was found, but it's too old, maybe re-fetch from cache source //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
refreshCacheIfExpired(getInput, getOutput);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private QRecord tryToGetFromCacheSource(GetInput getInput) throws QException
|
||||
{
|
||||
QRecord recordFromSource = null;
|
||||
QTableMetaData table = getInput.getTable();
|
||||
|
||||
for(CacheUseCase cacheUseCase : CollectionUtils.nonNullList(table.getCacheOf().getUseCases()))
|
||||
{
|
||||
if(CacheUseCase.Type.UNIQUE_KEY_TO_UNIQUE_KEY.equals(cacheUseCase.getType()) && getInput.getUniqueKey() != null)
|
||||
{
|
||||
recordFromSource = getFromCachedSourceForUniqueKeyToUniqueKey(getInput, table.getCacheOf().getSourceTable());
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo!!
|
||||
throw new NotImplementedException("Not-yet-implemented cache use case type: " + cacheUseCase.getType());
|
||||
}
|
||||
}
|
||||
|
||||
return (recordFromSource);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void refreshCacheIfExpired(GetInput getInput, GetOutput getOutput) throws QException
|
||||
{
|
||||
QTableMetaData table = getInput.getTable();
|
||||
Integer expirationSeconds = table.getCacheOf().getExpirationSeconds();
|
||||
if(expirationSeconds != null)
|
||||
{
|
||||
QRecord cachedRecord = getOutput.getRecord();
|
||||
Instant cachedDate = cachedRecord.getValueInstant(table.getCacheOf().getCachedDateFieldName());
|
||||
if(cachedDate == null || cachedDate.isBefore(Instant.now().minus(expirationSeconds, ChronoUnit.SECONDS)))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// keep the serial key from the old record in case we need to delete it //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
Serializable oldRecordPrimaryKey = cachedRecord.getValue(table.getPrimaryKeyField());
|
||||
boolean shouldDeleteCachedRecord;
|
||||
|
||||
///////////////////////////////////////////
|
||||
// fetch record from original source now //
|
||||
///////////////////////////////////////////
|
||||
QRecord recordFromSource = tryToGetFromCacheSource(getInput);
|
||||
if(recordFromSource != null)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// if the record was found in the source, put it into the output //
|
||||
// object so returned back to caller //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
QRecord recordToCache = CacheUtils.mapSourceRecordToCacheRecord(table, recordFromSource);
|
||||
recordToCache.setValue(table.getPrimaryKeyField(), cachedRecord.getValue(table.getPrimaryKeyField()));
|
||||
getOutput.setRecord(recordToCache);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the record should be cached, update the cache record - else set the flag to delete the cached record. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(CacheUtils.shouldCacheRecord(table, recordToCache))
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(getInput.getTableName());
|
||||
updateInput.setRecords(List.of(recordToCache));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
getOutput.setRecord(updateOutput.getRecords().get(0));
|
||||
shouldDeleteCachedRecord = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldDeleteCachedRecord = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we did not get a record back from the source, empty out the getOutput's record //
|
||||
// and set the flag to delete the cached record //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
getOutput.setRecord(null);
|
||||
shouldDeleteCachedRecord = true;
|
||||
}
|
||||
|
||||
if(shouldDeleteCachedRecord)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the record is no longer in the source (or it was in the source, but failed the should-cache check), then remove it from the cache //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(getInput.getTableName());
|
||||
deleteInput.setPrimaryKeys(List.of(oldRecordPrimaryKey));
|
||||
new DeleteAction().execute(deleteInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private QRecord getFromCachedSourceForUniqueKeyToUniqueKey(GetInput getInput, String sourceTableName) throws QException
|
||||
{
|
||||
/////////////////////////////////////////////////////
|
||||
// do a Get on the source table, by the unique key //
|
||||
/////////////////////////////////////////////////////
|
||||
GetInput sourceGetInput = new GetInput();
|
||||
sourceGetInput.setTableName(sourceTableName);
|
||||
sourceGetInput.setUniqueKey(getInput.getUniqueKey());
|
||||
GetOutput sourceGetOutput = new GetAction().execute(sourceGetInput);
|
||||
QRecord outputRecord = sourceGetOutput.getRecord();
|
||||
|
||||
return (outputRecord);
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
@ -0,0 +1,622 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.tables.helpers;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
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.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
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.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.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** After running a query, if it's for a table that's a CacheOf another table,
|
||||
** see if there are any cache use-cases to apply to the query result.
|
||||
**
|
||||
** Such as:
|
||||
** - if it's a query for one or more values in a UniqueKey:
|
||||
** - if any particular UniqueKeys weren't found, look in the source table
|
||||
** - if any cached records are expired, refresh them from the source
|
||||
** - possibly updating the cached record; possibly deleting it.
|
||||
*******************************************************************************/
|
||||
public class QueryActionCacheHelper
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QueryActionCacheHelper.class);
|
||||
|
||||
private boolean isQueryInputCacheable = false;
|
||||
private Map<CacheUseCase.Type, CacheUseCase> cacheUseCaseMap = new HashMap<>();
|
||||
private CacheUseCase activeCacheUseCase = null;
|
||||
|
||||
private UniqueKey cacheUniqueKey = null;
|
||||
private ListingHash<String, Serializable> uniqueKeyValues = new ListingHash<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void handleCaching(QueryInput queryInput, QueryOutput queryOutput) throws QException
|
||||
{
|
||||
analyzeInput(queryInput);
|
||||
if(!isQueryInputCacheable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// figure out which keys in the query were found, and which were missed //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> recordsFoundInCache = new ArrayList<>(queryOutput.getRecords());
|
||||
Set<List<Serializable>> uniqueKeyValuesInFoundRecords = getUniqueKeyValuesFromFoundRecords(queryOutput.getRecords());
|
||||
Set<List<Serializable>> missedUniqueKeyValues = getUniqueKeyValuesFromQuery();
|
||||
missedUniqueKeyValues.removeAll(uniqueKeyValuesInFoundRecords);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// if any requested records weren't found, see if we should look in cache-source //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(missedUniqueKeyValues))
|
||||
{
|
||||
List<QRecord> recordsFromSource = tryToGetFromCacheSource(queryInput, missedUniqueKeyValues);
|
||||
if(CollectionUtils.nullSafeHasContents(recordsFromSource))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// good, we found records from the source, make sure we should cache them, and if so, do it now //
|
||||
// note, we always return the record from the source, even if we don't cache it. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QTableMetaData table = queryInput.getTable();
|
||||
|
||||
List<QRecord> recordsToReturn = recordsFromSource.stream()
|
||||
.map(r -> CacheUtils.mapSourceRecordToCacheRecord(table, r, activeCacheUseCase))
|
||||
.toList();
|
||||
queryOutput.addRecords(recordsToReturn);
|
||||
|
||||
List<QRecord> recordsToCache = recordsToReturn.stream()
|
||||
.filter(r -> CacheUtils.shouldCacheRecord(table, r))
|
||||
.toList();
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(recordsToCache))
|
||||
{
|
||||
try
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(queryInput.getTableName());
|
||||
insertInput.setRecords(recordsToCache);
|
||||
insertInput.setSkipUniqueKeyCheck(true);
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// set the (generated) ids in the records being returne //
|
||||
//////////////////////////////////////////////////////////
|
||||
Map<List<Serializable>, QRecord> insertedRecordsByUniqueKey = new HashMap<>();
|
||||
for(QRecord record : insertOutput.getRecords())
|
||||
{
|
||||
insertedRecordsByUniqueKey.put(getUniqueKeyValues(record), record);
|
||||
}
|
||||
for(QRecord record : recordsToReturn)
|
||||
{
|
||||
QRecord insertedRecord = insertedRecordsByUniqueKey.get(getUniqueKeyValues(record));
|
||||
if(insertedRecord != null)
|
||||
{
|
||||
record.setValue(table.getPrimaryKeyField(), insertedRecord.getValue(table.getPrimaryKeyField()));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// don't let an exception break this query - it (probably) just indicates some data that didn't get cached - so - that's generally "ok" //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
LOG.warn("Error inserting cached records", e, logPair("cacheTable", queryInput.getTableName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// for records that were found, if they're too old, maybe re-fetch them //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(recordsFoundInCache))
|
||||
{
|
||||
refreshCacheIfExpired(recordsFoundInCache, queryInput, queryOutput);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void refreshCacheIfExpired(List<QRecord> recordsFoundInCache, QueryInput queryInput, QueryOutput queryOutput) throws QException
|
||||
{
|
||||
QTableMetaData table = queryInput.getTable();
|
||||
Integer expirationSeconds = table.getCacheOf().getExpirationSeconds();
|
||||
|
||||
if(expirationSeconds != null)
|
||||
{
|
||||
List<QRecord> expiredRecords = new ArrayList<>();
|
||||
for(QRecord cachedRecord : recordsFoundInCache)
|
||||
{
|
||||
Instant cachedDate = cachedRecord.getValueInstant(table.getCacheOf().getCachedDateFieldName());
|
||||
if(cachedDate == null || cachedDate.isBefore(Instant.now().minus(expirationSeconds, ChronoUnit.SECONDS)))
|
||||
{
|
||||
expiredRecords.add(cachedRecord);
|
||||
}
|
||||
}
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(expiredRecords))
|
||||
{
|
||||
Map<List<Serializable>, Serializable> uniqueKeyToPrimaryKeyMap = getUniqueKeyToPrimaryKeyMap(table.getPrimaryKeyField(), expiredRecords);
|
||||
Set<List<Serializable>> uniqueKeyValuesToRefresh = uniqueKeyToPrimaryKeyMap.keySet();
|
||||
|
||||
////////////////////////////////////////////
|
||||
// fetch records from original source now //
|
||||
////////////////////////////////////////////
|
||||
List<QRecord> recordsFromSource = tryToGetFromCacheSource(queryInput, uniqueKeyValuesToRefresh);
|
||||
|
||||
Set<List<Serializable>> uniqueKeyValuesInFoundRecords = getUniqueKeyValuesFromFoundRecords(recordsFromSource);
|
||||
Set<List<Serializable>> missedUniqueKeyValues = getUniqueKeyValuesFromQuery();
|
||||
missedUniqueKeyValues.retainAll(getUniqueKeyValuesFromFoundRecords(expiredRecords));
|
||||
missedUniqueKeyValues.removeAll(uniqueKeyValuesInFoundRecords);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// build records to cache - setting their original (from cache) ids back in them, so they'll update //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> refreshedRecordsToReturn = recordsFromSource.stream()
|
||||
.map(r ->
|
||||
{
|
||||
QRecord recordToCache = CacheUtils.mapSourceRecordToCacheRecord(table, r, activeCacheUseCase);
|
||||
recordToCache.setValue(table.getPrimaryKeyField(), uniqueKeyToPrimaryKeyMap.get(getUniqueKeyValues(recordToCache)));
|
||||
return (recordToCache);
|
||||
})
|
||||
.toList();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the records were found in the source, put it into the output object so returned back to caller //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Map<List<Serializable>, QRecord> refreshedRecordsByUniqueKeyValues = refreshedRecordsToReturn.stream().collect(Collectors.toMap(this::getUniqueKeyValues, r -> r, (a, b) -> a));
|
||||
|
||||
ListIterator<QRecord> queryOutputListIterator = queryOutput.getRecords().listIterator();
|
||||
while(queryOutputListIterator.hasNext())
|
||||
{
|
||||
QRecord originalRecord = queryOutputListIterator.next();
|
||||
List<Serializable> recordUniqueKeyValues = getUniqueKeyValues(originalRecord);
|
||||
QRecord refreshedRecord = refreshedRecordsByUniqueKeyValues.get(recordUniqueKeyValues);
|
||||
|
||||
if(refreshedRecord != null)
|
||||
{
|
||||
queryOutputListIterator.set(refreshedRecord);
|
||||
}
|
||||
else if(missedUniqueKeyValues.contains(recordUniqueKeyValues))
|
||||
{
|
||||
queryOutputListIterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// for refreshed records which should be cached, update them in the cache //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> recordsToUpdate = refreshedRecordsToReturn.stream().filter(r -> CacheUtils.shouldCacheRecord(table, r)).toList();
|
||||
if(CollectionUtils.nullSafeHasContents(recordsToUpdate))
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(queryInput.getTableName());
|
||||
updateInput.setRecords(recordsToUpdate);
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// don't let an exception break this query - it (probably) just indicates some data that didn't get cached - so - that's generally "ok" //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
LOG.warn("Error updating cached records", e, logPair("cacheTable", queryInput.getTableName()));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the records were missed in the source - OR if they shouldn't be cached now, then mark them for deleting //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Set<Serializable> cachedRecordIdsToDelete = missedUniqueKeyValues.stream()
|
||||
.map(uniqueKeyToPrimaryKeyMap::get)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
cachedRecordIdsToDelete.addAll(refreshedRecordsToReturn.stream()
|
||||
.filter(r -> !CacheUtils.shouldCacheRecord(table, r))
|
||||
.map(r -> r.getValue(table.getPrimaryKeyField()))
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(cachedRecordIdsToDelete))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// if the records are no longer in the source, then remove them from the cache //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
try
|
||||
{
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(queryInput.getTableName());
|
||||
deleteInput.setPrimaryKeys(new ArrayList<>(cachedRecordIdsToDelete));
|
||||
new DeleteAction().execute(deleteInput);
|
||||
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// don't let an exception break this query - it (probably) just indicates some data that didn't get uncached - so - that's generally "ok" //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
LOG.warn("Error deleting cached records", e, logPair("cacheTable", queryInput.getTableName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Set<List<Serializable>> getUniqueKeyValuesFromQuery()
|
||||
{
|
||||
Set<List<Serializable>> rs = new HashSet<>();
|
||||
|
||||
int noOfUniqueKeys = uniqueKeyValues.get(cacheUniqueKey.getFieldNames().get(0)).size();
|
||||
for(int i = 0; i < noOfUniqueKeys; i++)
|
||||
{
|
||||
List<Serializable> values = new ArrayList<>();
|
||||
|
||||
for(String fieldName : cacheUniqueKey.getFieldNames())
|
||||
{
|
||||
values.add(uniqueKeyValues.get(fieldName).get(i));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// critical - leave this here so hashCode from the list is correctly computed //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
rs.add(values);
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Set<List<Serializable>> getUniqueKeyValuesFromFoundRecords(List<QRecord> records)
|
||||
{
|
||||
return (getUniqueKeyToPrimaryKeyMap("ignore", records).keySet());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Map<List<Serializable>, Serializable> getUniqueKeyToPrimaryKeyMap(String primaryKeyField, List<QRecord> records)
|
||||
{
|
||||
Map<List<Serializable>, Serializable> rs = new HashMap<>();
|
||||
|
||||
for(QRecord record : records)
|
||||
{
|
||||
List<Serializable> uniqueKeyValues = getUniqueKeyValues(record);
|
||||
rs.put(uniqueKeyValues, record.getValue(primaryKeyField));
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<Serializable> getUniqueKeyValues(QRecord record)
|
||||
{
|
||||
List<Serializable> uniqueKeyValues = new ArrayList<>();
|
||||
for(String fieldName : cacheUniqueKey.getFieldNames())
|
||||
{
|
||||
uniqueKeyValues.add(record.getValue(fieldName));
|
||||
}
|
||||
return uniqueKeyValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** figure out if this was a request that we can cache records for -
|
||||
** e.g., if it's a request for unique-key EQUALS or IN
|
||||
** build up fields for the unique keys, the values, etc.
|
||||
*******************************************************************************/
|
||||
private void analyzeInput(QueryInput queryInput)
|
||||
{
|
||||
QTableMetaData table = queryInput.getTable();
|
||||
|
||||
for(CacheUseCase cacheUseCase : CollectionUtils.nonNullList(table.getCacheOf().getUseCases()))
|
||||
{
|
||||
cacheUseCaseMap.put(cacheUseCase.getType(), cacheUseCase);
|
||||
}
|
||||
|
||||
if(cacheUseCaseMap.containsKey(CacheUseCase.Type.UNIQUE_KEY_TO_UNIQUE_KEY))
|
||||
{
|
||||
if(queryInput.getFilter() == null)
|
||||
{
|
||||
LOG.trace("Unable to cache: there is no filter");
|
||||
return;
|
||||
}
|
||||
|
||||
QQueryFilter filter = queryInput.getFilter();
|
||||
Set<String> queryFields = new HashSet<>();
|
||||
if(CollectionUtils.nullSafeHasContents(filter.getSubFilters()))
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(filter.getCriteria()))
|
||||
{
|
||||
LOG.trace("Unable to cache: we have sub-filters and criteria");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!QQueryFilter.BooleanOperator.OR.equals(filter.getBooleanOperator()))
|
||||
{
|
||||
LOG.trace("Unable to cache: we have sub-filters but not an OR query");
|
||||
return;
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// look at sub-filters //
|
||||
/////////////////////////
|
||||
for(QQueryFilter subFilter : filter.getSubFilters())
|
||||
{
|
||||
Set<String> thisSubFilterFields = getQueryFieldsIfCacheableFilter(subFilter, false);
|
||||
if(thisSubFilterFields == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(queryFields.isEmpty())
|
||||
{
|
||||
queryFields.addAll(thisSubFilterFields);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!queryFields.equals(thisSubFilterFields))
|
||||
{
|
||||
LOG.trace("Unable to cache: sub-filters have different sets of fields");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(doQueryFieldsMatchAUniqueKey(table, queryFields))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.trace("Unable to cache: we have sub-filters that do match a unique key");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//////////////////////////////////////////
|
||||
// look at the criteria in the query: //
|
||||
// - build a set of field names //
|
||||
// - fail upon unsupported operators //
|
||||
// - collect the values in the criteria //
|
||||
//////////////////////////////////////////
|
||||
queryFields = getQueryFieldsIfCacheableFilter(filter, true);
|
||||
if(queryFields == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(doQueryFieldsMatchAUniqueKey(table, queryFields))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.trace("Unable to cache: we have query fields that don't match a unique key: " + queryFields);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.trace("Unable to cache: No supported use case: " + cacheUseCaseMap.keySet());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private boolean doQueryFieldsMatchAUniqueKey(QTableMetaData table, Set<String> queryFields)
|
||||
{
|
||||
for(UniqueKey uniqueKey : CollectionUtils.nonNullList(table.getUniqueKeys()))
|
||||
{
|
||||
if(queryFields.equals(new HashSet<>(uniqueKey.getFieldNames())))
|
||||
{
|
||||
this.cacheUniqueKey = uniqueKey;
|
||||
isQueryInputCacheable = true;
|
||||
activeCacheUseCase = cacheUseCaseMap.get(CacheUseCase.Type.UNIQUE_KEY_TO_UNIQUE_KEY);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Set<String> getQueryFieldsIfCacheableFilter(QQueryFilter filter, boolean allowOperatorIn)
|
||||
{
|
||||
Set<String> rs = new HashSet<>();
|
||||
for(QFilterCriteria criterion : filter.getCriteria())
|
||||
{
|
||||
boolean isEquals = criterion.getOperator().equals(QCriteriaOperator.EQUALS);
|
||||
boolean isIn = criterion.getOperator().equals(QCriteriaOperator.IN);
|
||||
|
||||
if(isEquals || (isIn && allowOperatorIn))
|
||||
{
|
||||
rs.add(criterion.getFieldName());
|
||||
this.uniqueKeyValues.addAll(criterion.getFieldName(), criterion.getValues());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.trace("Unable to cache: we have an unsupported criteria operator: " + criterion.getOperator());
|
||||
isQueryInputCacheable = false;
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<QRecord> tryToGetFromCacheSource(QueryInput queryInput, Set<List<Serializable>> uniqueKeyValues) throws QException
|
||||
{
|
||||
List<QRecord> recordsFromSource = null;
|
||||
QTableMetaData table = queryInput.getTable();
|
||||
|
||||
if(CacheUseCase.Type.UNIQUE_KEY_TO_UNIQUE_KEY.equals(activeCacheUseCase.getType()))
|
||||
{
|
||||
recordsFromSource = getFromCachedSourceForUniqueKeyToUniqueKey(queryInput, uniqueKeyValues, table.getCacheOf().getSourceTable());
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo!!
|
||||
throw (new NotImplementedException("Not-yet-implemented cache use case type: " + activeCacheUseCase.getType()));
|
||||
}
|
||||
|
||||
return (recordsFromSource);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<QRecord> getFromCachedSourceForUniqueKeyToUniqueKey(QueryInput cacheQueryInput, Set<List<Serializable>> uniqueKeyValues, String sourceTableName) throws QException
|
||||
{
|
||||
QTableMetaData sourceTable = QContext.getQInstance().getTable(sourceTableName);
|
||||
QBackendMetaData sourceBackend = QContext.getQInstance().getBackendForTable(sourceTableName);
|
||||
|
||||
if(sourceTable.isCapabilityEnabled(sourceBackend, Capability.TABLE_QUERY))
|
||||
{
|
||||
///////////////////////////////////////////////////////
|
||||
// do a Query on the source table, by the unique key //
|
||||
///////////////////////////////////////////////////////
|
||||
QueryInput sourceQueryInput = new QueryInput();
|
||||
sourceQueryInput.setTableName(sourceTableName);
|
||||
|
||||
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR);
|
||||
sourceQueryInput.setFilter(filter);
|
||||
sourceQueryInput.setCommonParamsFrom(cacheQueryInput);
|
||||
|
||||
for(List<Serializable> uniqueKeyValue : uniqueKeyValues)
|
||||
{
|
||||
QQueryFilter subFilter = new QQueryFilter();
|
||||
filter.addSubFilter(subFilter);
|
||||
|
||||
for(int i = 0; i < cacheUniqueKey.getFieldNames().size(); i++)
|
||||
{
|
||||
subFilter.addCriteria(new QFilterCriteria(cacheUniqueKey.getFieldNames().get(i), QCriteriaOperator.EQUALS, uniqueKeyValue.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
QueryOutput sourceQueryOutput = new QueryAction().execute(sourceQueryInput);
|
||||
return (sourceQueryOutput.getRecords());
|
||||
}
|
||||
else if(sourceTable.isCapabilityEnabled(sourceBackend, Capability.TABLE_GET))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// if the table only supports GET, then do a GET for each unique key //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> outputRecords = new ArrayList<>();
|
||||
for(List<Serializable> uniqueKeyValue : uniqueKeyValues)
|
||||
{
|
||||
Map<String, Serializable> uniqueKey = new HashMap<>();
|
||||
for(int i = 0; i < cacheUniqueKey.getFieldNames().size(); i++)
|
||||
{
|
||||
uniqueKey.put(cacheUniqueKey.getFieldNames().get(i), uniqueKeyValue.get(i));
|
||||
}
|
||||
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(sourceTableName);
|
||||
getInput.setUniqueKey(uniqueKey);
|
||||
getInput.setCommonParamsFrom(cacheQueryInput);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
|
||||
if(getOutput.getRecord() != null)
|
||||
{
|
||||
outputRecords.add(getOutput.getRecord());
|
||||
}
|
||||
}
|
||||
|
||||
return (outputRecords);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QException("Cache source table " + sourceTableName + " does not support Query or Get capability."));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,597 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.tables.helpers;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
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.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.querystats.QueryStat;
|
||||
import com.kingsrook.qqq.backend.core.model.querystats.QueryStatCriteriaField;
|
||||
import com.kingsrook.qqq.backend.core.model.querystats.QueryStatJoinTable;
|
||||
import com.kingsrook.qqq.backend.core.model.querystats.QueryStatOrderByField;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTableAccessor;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton, which starts a thread, to store query stats into a table.
|
||||
**
|
||||
** Supports these systemProperties or ENV_VARS:
|
||||
** qqq.queryStatManager.enabled / QQQ_QUERY_STAT_MANAGER_ENABLED
|
||||
** qqq.queryStatManager.minMillisToStore / QQQ_QUERY_STAT_MANAGER_MIN_MILLIS_TO_STORE
|
||||
** qqq.queryStatManager.jobPeriodSeconds / QQQ_QUERY_STAT_MANAGER_JOB_PERIOD_SECONDS
|
||||
** qqq.queryStatManager.jobInitialDelay / QQQ_QUERY_STAT_MANAGER_JOB_INITIAL_DELAY
|
||||
*******************************************************************************/
|
||||
public class QueryStatManager
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QueryStatManager.class);
|
||||
|
||||
private static QueryStatManager queryStatManager = null;
|
||||
|
||||
// todo - support multiple qInstances?
|
||||
private QInstance qInstance;
|
||||
private Supplier<QSession> sessionSupplier;
|
||||
|
||||
private boolean active = false;
|
||||
private List<QueryStat> queryStats = new ArrayList<>();
|
||||
|
||||
private ScheduledExecutorService executorService;
|
||||
|
||||
private int jobPeriodSeconds = 60;
|
||||
private int jobInitialDelay = 60;
|
||||
private int minMillisToStore = 0;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton constructor
|
||||
*******************************************************************************/
|
||||
private QueryStatManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton accessor
|
||||
*******************************************************************************/
|
||||
public static QueryStatManager getInstance()
|
||||
{
|
||||
if(queryStatManager == null)
|
||||
{
|
||||
queryStatManager = new QueryStatManager();
|
||||
|
||||
QMetaDataVariableInterpreter interpreter = new QMetaDataVariableInterpreter();
|
||||
|
||||
Integer propertyMinMillisToStore = interpreter.getIntegerFromPropertyOrEnvironment("qqq.queryStatManager.minMillisToStore", "QQQ_QUERY_STAT_MANAGER_MIN_MILLIS_TO_STORE", null);
|
||||
if(propertyMinMillisToStore != null)
|
||||
{
|
||||
queryStatManager.setMinMillisToStore(propertyMinMillisToStore);
|
||||
}
|
||||
|
||||
Integer propertyJobPeriodSeconds = interpreter.getIntegerFromPropertyOrEnvironment("qqq.queryStatManager.jobPeriodSeconds", "QQQ_QUERY_STAT_MANAGER_JOB_PERIOD_SECONDS", null);
|
||||
if(propertyJobPeriodSeconds != null)
|
||||
{
|
||||
queryStatManager.setJobPeriodSeconds(propertyJobPeriodSeconds);
|
||||
}
|
||||
|
||||
Integer propertyJobInitialDelay = interpreter.getIntegerFromPropertyOrEnvironment("qqq.queryStatManager.jobInitialDelay", "QQQ_QUERY_STAT_MANAGER_JOB_INITIAL_DELAY", null);
|
||||
if(propertyJobInitialDelay != null)
|
||||
{
|
||||
queryStatManager.setJobInitialDelay(propertyJobInitialDelay);
|
||||
}
|
||||
|
||||
}
|
||||
return (queryStatManager);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QueryStat newQueryStat(QBackendMetaData backend, QTableMetaData table, QQueryFilter filter)
|
||||
{
|
||||
QueryStat queryStat = null;
|
||||
|
||||
if(table.isCapabilityEnabled(backend, Capability.QUERY_STATS))
|
||||
{
|
||||
queryStat = new QueryStat();
|
||||
queryStat.setTableName(table.getName());
|
||||
queryStat.setQueryFilter(Objects.requireNonNullElse(filter, new QQueryFilter()));
|
||||
queryStat.setStartTimestamp(Instant.now());
|
||||
}
|
||||
|
||||
return (queryStat);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void start(QInstance qInstance, Supplier<QSession> sessionSupplier)
|
||||
{
|
||||
if(!isEnabled())
|
||||
{
|
||||
LOG.info("Not starting QueryStatManager per settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("Starting QueryStatManager");
|
||||
|
||||
this.qInstance = qInstance;
|
||||
this.sessionSupplier = sessionSupplier;
|
||||
|
||||
active = true;
|
||||
queryStats = new ArrayList<>();
|
||||
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
executorService.scheduleAtFixedRate(new QueryStatManagerInsertJob(), jobInitialDelay, jobPeriodSeconds, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static boolean isEnabled()
|
||||
{
|
||||
return new QMetaDataVariableInterpreter().getBooleanFromPropertyOrEnvironment("qqq.queryStatManager.enabled", "QQQ_QUERY_STAT_MANAGER_ENABLED", true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void stop()
|
||||
{
|
||||
active = false;
|
||||
queryStats.clear();
|
||||
|
||||
if(executorService != null)
|
||||
{
|
||||
executorService.shutdown();
|
||||
executorService = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void add(QueryStat queryStat)
|
||||
{
|
||||
if(queryStat == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(active)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// set fields that we need to capture now (rather than when the thread to store runs) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(queryStat.getFirstResultTimestamp() == null)
|
||||
{
|
||||
queryStat.setFirstResultTimestamp(Instant.now());
|
||||
}
|
||||
|
||||
if(queryStat.getStartTimestamp() != null && queryStat.getFirstResultTimestamp() != null && queryStat.getFirstResultMillis() == null)
|
||||
{
|
||||
long millis = queryStat.getFirstResultTimestamp().toEpochMilli() - queryStat.getStartTimestamp().toEpochMilli();
|
||||
queryStat.setFirstResultMillis((int) millis);
|
||||
}
|
||||
|
||||
if(queryStat.getFirstResultMillis() != null && queryStat.getFirstResultMillis() < minMillisToStore)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////
|
||||
// discard this record if it's under the min millis setting //
|
||||
//////////////////////////////////////////////////////////////
|
||||
return;
|
||||
}
|
||||
|
||||
if(queryStat.getSessionId() == null && QContext.getQSession() != null)
|
||||
{
|
||||
queryStat.setSessionId(QContext.getQSession().getUuid());
|
||||
}
|
||||
|
||||
if(queryStat.getAction() == null)
|
||||
{
|
||||
if(!QContext.getActionStack().isEmpty())
|
||||
{
|
||||
queryStat.setAction(QContext.getActionStack().peek().getActionIdentity());
|
||||
}
|
||||
else
|
||||
{
|
||||
boolean expected = false;
|
||||
Exception e = new Exception("Unexpected empty action stack");
|
||||
for(StackTraceElement stackTraceElement : e.getStackTrace())
|
||||
{
|
||||
String className = stackTraceElement.getClassName();
|
||||
if(className.contains(QueryStatManagerInsertJob.class.getName()))
|
||||
{
|
||||
expected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!expected)
|
||||
{
|
||||
LOG.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
queryStats.add(queryStat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<QueryStat> getListAndReset()
|
||||
{
|
||||
if(queryStats.isEmpty())
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
List<QueryStat> returnList = queryStats;
|
||||
queryStats = new ArrayList<>();
|
||||
return (returnList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** force stats to be stored right now (rather than letting the scheduled job do it)
|
||||
*******************************************************************************/
|
||||
public void storeStatsNow()
|
||||
{
|
||||
new QueryStatManagerInsertJob().run();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Runnable that gets scheduled to periodically reset and store the list of collected stats
|
||||
*******************************************************************************/
|
||||
private static class QueryStatManagerInsertJob implements Runnable
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QueryStatManagerInsertJob.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
QContext.init(getInstance().qInstance, getInstance().sessionSupplier.get());
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// every time we re-run, check if we've been turned off - if so, stop the service. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
if(!isEnabled())
|
||||
{
|
||||
LOG.info("Stopping QueryStatManager.");
|
||||
getInstance().stop();
|
||||
return;
|
||||
}
|
||||
|
||||
List<QueryStat> list = getInstance().getListAndReset();
|
||||
|
||||
LOG.info(logPair("queryStatListSize", list.size()));
|
||||
|
||||
if(list.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// prime the entities for storing //
|
||||
////////////////////////////////////
|
||||
List<QRecord> queryStatQRecordsToInsert = new ArrayList<>();
|
||||
for(QueryStat queryStat : list)
|
||||
{
|
||||
try
|
||||
{
|
||||
//////////////////////
|
||||
// set the table id //
|
||||
//////////////////////
|
||||
Integer tableId = QQQTableAccessor.getTableId(queryStat.getTableName());
|
||||
queryStat.setTableId(tableId);
|
||||
|
||||
//////////////////////////////
|
||||
// build join-table records //
|
||||
//////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(queryStat.getJoinTableNames()))
|
||||
{
|
||||
List<QueryStatJoinTable> queryStatJoinTableList = new ArrayList<>();
|
||||
for(String joinTableName : queryStat.getJoinTableNames())
|
||||
{
|
||||
queryStatJoinTableList.add(new QueryStatJoinTable().withTableId(QQQTableAccessor.getTableId(joinTableName)));
|
||||
}
|
||||
queryStat.setQueryStatJoinTableList(queryStatJoinTableList);
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// build criteria records //
|
||||
////////////////////////////
|
||||
if(queryStat.getQueryFilter() != null && queryStat.getQueryFilter().hasAnyCriteria())
|
||||
{
|
||||
List<QueryStatCriteriaField> queryStatCriteriaFieldList = new ArrayList<>();
|
||||
processCriteriaFromFilter(tableId, queryStatCriteriaFieldList, queryStat.getQueryFilter());
|
||||
queryStat.setQueryStatCriteriaFieldList(queryStatCriteriaFieldList);
|
||||
}
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(queryStat.getQueryFilter().getOrderBys()))
|
||||
{
|
||||
List<QueryStatOrderByField> queryStatOrderByFieldList = new ArrayList<>();
|
||||
processOrderByFromFilter(tableId, queryStatOrderByFieldList, queryStat.getQueryFilter());
|
||||
queryStat.setQueryStatOrderByFieldList(queryStatOrderByFieldList);
|
||||
}
|
||||
|
||||
queryStatQRecordsToInsert.add(queryStat.toQRecord());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//////////////////////
|
||||
// skip this record //
|
||||
//////////////////////
|
||||
LOG.warn("Error priming a query stat for storing", e);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(QueryStat.TABLE_NAME);
|
||||
insertInput.setRecords(queryStatQRecordsToInsert);
|
||||
new InsertAction().execute(insertInput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error inserting query stats", e);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error storing query stats", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processCriteriaFromFilter(Integer tableId, List<QueryStatCriteriaField> queryStatCriteriaFieldList, QQueryFilter queryFilter) throws QException
|
||||
{
|
||||
for(QFilterCriteria criteria : CollectionUtils.nonNullList(queryFilter.getCriteria()))
|
||||
{
|
||||
String fieldName = criteria.getFieldName();
|
||||
QueryStatCriteriaField queryStatCriteriaField = new QueryStatCriteriaField();
|
||||
queryStatCriteriaField.setOperator(String.valueOf(criteria.getOperator()));
|
||||
|
||||
if(criteria.getValues() != null)
|
||||
{
|
||||
queryStatCriteriaField.setValues(StringUtils.join(",", criteria.getValues()));
|
||||
}
|
||||
|
||||
if(fieldName.contains("."))
|
||||
{
|
||||
String[] parts = fieldName.split("\\.");
|
||||
if(parts.length > 1)
|
||||
{
|
||||
queryStatCriteriaField.setTableId(QQQTableAccessor.getTableId(parts[0]));
|
||||
queryStatCriteriaField.setName(parts[1]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queryStatCriteriaField.setTableId(tableId);
|
||||
queryStatCriteriaField.setName(fieldName);
|
||||
}
|
||||
|
||||
queryStatCriteriaFieldList.add(queryStatCriteriaField);
|
||||
}
|
||||
|
||||
for(QQueryFilter subFilter : CollectionUtils.nonNullList(queryFilter.getSubFilters()))
|
||||
{
|
||||
processCriteriaFromFilter(tableId, queryStatCriteriaFieldList, subFilter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processOrderByFromFilter(Integer tableId, List<QueryStatOrderByField> queryStatOrderByFieldList, QQueryFilter queryFilter) throws QException
|
||||
{
|
||||
for(QFilterOrderBy orderBy : CollectionUtils.nonNullList(queryFilter.getOrderBys()))
|
||||
{
|
||||
String fieldName = orderBy.getFieldName();
|
||||
QueryStatOrderByField queryStatOrderByField = new QueryStatOrderByField();
|
||||
|
||||
if(fieldName != null)
|
||||
{
|
||||
if(fieldName.contains("."))
|
||||
{
|
||||
String[] parts = fieldName.split("\\.");
|
||||
if(parts.length > 1)
|
||||
{
|
||||
queryStatOrderByField.setTableId(QQQTableAccessor.getTableId(parts[0]));
|
||||
queryStatOrderByField.setName(parts[1]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queryStatOrderByField.setTableId(tableId);
|
||||
queryStatOrderByField.setName(fieldName);
|
||||
}
|
||||
|
||||
queryStatOrderByFieldList.add(queryStatOrderByField);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for jobPeriodSeconds
|
||||
*******************************************************************************/
|
||||
public int getJobPeriodSeconds()
|
||||
{
|
||||
return (this.jobPeriodSeconds);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for jobPeriodSeconds
|
||||
*******************************************************************************/
|
||||
public void setJobPeriodSeconds(int jobPeriodSeconds)
|
||||
{
|
||||
this.jobPeriodSeconds = jobPeriodSeconds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for jobPeriodSeconds
|
||||
*******************************************************************************/
|
||||
public QueryStatManager withJobPeriodSeconds(int jobPeriodSeconds)
|
||||
{
|
||||
this.jobPeriodSeconds = jobPeriodSeconds;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for jobInitialDelay
|
||||
*******************************************************************************/
|
||||
public int getJobInitialDelay()
|
||||
{
|
||||
return (this.jobInitialDelay);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for jobInitialDelay
|
||||
*******************************************************************************/
|
||||
public void setJobInitialDelay(int jobInitialDelay)
|
||||
{
|
||||
this.jobInitialDelay = jobInitialDelay;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for jobInitialDelay
|
||||
*******************************************************************************/
|
||||
public QueryStatManager withJobInitialDelay(int jobInitialDelay)
|
||||
{
|
||||
this.jobInitialDelay = jobInitialDelay;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for minMillisToStore
|
||||
*******************************************************************************/
|
||||
public int getMinMillisToStore()
|
||||
{
|
||||
return (this.minMillisToStore);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for minMillisToStore
|
||||
*******************************************************************************/
|
||||
public void setMinMillisToStore(int minMillisToStore)
|
||||
{
|
||||
this.minMillisToStore = minMillisToStore;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for minMillisToStore
|
||||
*******************************************************************************/
|
||||
public QueryStatManager withMinMillisToStore(int minMillisToStore)
|
||||
{
|
||||
this.minMillisToStore = minMillisToStore;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -26,8 +26,8 @@ import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.templates.ConvertHtmlToPdfInput;
|
||||
import com.kingsrook.qqq.backend.core.model.templates.ConvertHtmlToPdfOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.templates.ConvertHtmlToPdfInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.templates.ConvertHtmlToPdfOutput;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
|
@ -28,8 +28,8 @@ import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.templates.RenderTemplateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.templates.RenderTemplateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.templates.RenderTemplateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.templates.RenderTemplateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.templates.TemplateType;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.velocity.VelocityContext;
|
||||
@ -62,7 +62,15 @@ public class RenderTemplateAction extends AbstractQActionFunction<RenderTemplate
|
||||
if(TemplateType.VELOCITY.equals(input.getTemplateType()))
|
||||
{
|
||||
Velocity.init();
|
||||
Context context = new VelocityContext(input.getContext());
|
||||
Context context = new VelocityContext();
|
||||
|
||||
if(input.getContext() != null)
|
||||
{
|
||||
for(Map.Entry<String, ?> entry : input.getContext().entrySet())
|
||||
{
|
||||
context.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
setupEventHandlers(context);
|
||||
|
||||
|
@ -136,7 +136,7 @@ public class QValueFormatter
|
||||
{
|
||||
return formatValue(displayFormat, ValueUtils.getValueAsBigDecimal(value));
|
||||
}
|
||||
else if(e.getMessage().equals("d != java.math.BigDecimal"))
|
||||
else if(e.getMessage().equals("d != java.math.BigDecimal") || e.getMessage().equals("d != java.lang.String"))
|
||||
{
|
||||
return formatValue(displayFormat, ValueUtils.getValueAsInteger(value));
|
||||
}
|
||||
|
@ -22,12 +22,19 @@
|
||||
package com.kingsrook.qqq.backend.core.exceptions;
|
||||
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Base class for checked exceptions thrown in qqq.
|
||||
*
|
||||
*******************************************************************************/
|
||||
public class QException extends Exception
|
||||
{
|
||||
private boolean hasLoggedWarning;
|
||||
private boolean hasLoggedError;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor of message
|
||||
@ -59,4 +66,102 @@ public class QException extends Exception
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for hasLoggedWarning
|
||||
*******************************************************************************/
|
||||
public boolean getHasLoggedWarning()
|
||||
{
|
||||
return (this.hasLoggedWarning);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for hasLoggedWarning
|
||||
*******************************************************************************/
|
||||
public void setHasLoggedWarning(boolean hasLoggedWarning)
|
||||
{
|
||||
this.hasLoggedWarning = hasLoggedWarning;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for hasLoggedWarning
|
||||
*******************************************************************************/
|
||||
public QException withHasLoggedWarning(boolean hasLoggedWarning)
|
||||
{
|
||||
this.hasLoggedWarning = hasLoggedWarning;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for hasLoggedError
|
||||
*******************************************************************************/
|
||||
public boolean getHasLoggedError()
|
||||
{
|
||||
return (this.hasLoggedError);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for hasLoggedError
|
||||
*******************************************************************************/
|
||||
public void setHasLoggedError(boolean hasLoggedError)
|
||||
{
|
||||
this.hasLoggedError = hasLoggedError;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for hasLoggedError
|
||||
*******************************************************************************/
|
||||
public QException withHasLoggedError(boolean hasLoggedError)
|
||||
{
|
||||
this.hasLoggedError = hasLoggedError;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** helper function for getting if level logged
|
||||
*******************************************************************************/
|
||||
public boolean hasLoggedLevel(Level level)
|
||||
{
|
||||
if(Level.WARN.equals(level))
|
||||
{
|
||||
return (hasLoggedWarning);
|
||||
}
|
||||
if(Level.ERROR.equals(level))
|
||||
{
|
||||
return (hasLoggedError);
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** helper function for setting if level logged
|
||||
*******************************************************************************/
|
||||
public void setHasLoggedLevel(Level level)
|
||||
{
|
||||
if(Level.WARN.equals(level))
|
||||
{
|
||||
setHasLoggedWarning(true);
|
||||
}
|
||||
if(Level.ERROR.equals(level))
|
||||
{
|
||||
setHasLoggedError(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@ -57,12 +58,13 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponen
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QSupplementalProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QMiddlewareTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QSupplementalTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.delete.BulkDeleteLoadStep;
|
||||
@ -96,6 +98,13 @@ public class QInstanceEnricher
|
||||
//////////////////////////////////////////////////////////
|
||||
private boolean configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels = true;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// let an instance define mappings to be applied during name-to-label enrichments, //
|
||||
// e.g., to avoid ever incorrectly camel-casing an acronym (e.g., "Tla" should always be "TLA") //
|
||||
// or to expand abbreviations in code (e.g., "Addr" should always be "Address" //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private static final Map<String, String> labelMappings = new LinkedHashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -261,9 +270,9 @@ public class QInstanceEnricher
|
||||
{
|
||||
table.getFields().values().forEach(this::enrichField);
|
||||
|
||||
for(QMiddlewareTableMetaData middlewareTableMetaData : CollectionUtils.nonNullMap(table.getMiddlewareMetaData()).values())
|
||||
for(QSupplementalTableMetaData supplementalTableMetaData : CollectionUtils.nonNullMap(table.getSupplementalMetaData()).values())
|
||||
{
|
||||
middlewareTableMetaData.enrich(table);
|
||||
supplementalTableMetaData.enrich(table);
|
||||
}
|
||||
}
|
||||
|
||||
@ -367,6 +376,11 @@ public class QInstanceEnricher
|
||||
process.getStepList().forEach(this::enrichStep);
|
||||
}
|
||||
|
||||
for(QSupplementalProcessMetaData supplementalProcessMetaData : CollectionUtils.nonNullMap(process.getSupplementalMetaData()).values())
|
||||
{
|
||||
supplementalProcessMetaData.enrich(this, process);
|
||||
}
|
||||
|
||||
enrichPermissionRules(process);
|
||||
}
|
||||
|
||||
@ -641,7 +655,17 @@ public class QInstanceEnricher
|
||||
////////////////////////////////////////////////////////////////
|
||||
.replaceAll("([0-9])([A-Za-z])", "$1 $2");
|
||||
|
||||
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + suffix);
|
||||
String label = name.substring(0, 1).toUpperCase(Locale.ROOT) + suffix;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// apply any label mappings - e.g., to force app-specific acronyms/initialisms to all-caps //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(Map.Entry<String, String> entry : labelMappings.entrySet())
|
||||
{
|
||||
label = label.replaceAll(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return (label);
|
||||
}
|
||||
|
||||
|
||||
@ -1105,4 +1129,35 @@ public class QInstanceEnricher
|
||||
{
|
||||
return (this.joinGraph);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void addLabelMapping(String from, String to)
|
||||
{
|
||||
labelMappings.put(from, to);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void removeLabelMapping(String from)
|
||||
{
|
||||
labelMappings.remove(from);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void clearLabelMappings()
|
||||
{
|
||||
labelMappings.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMiddlewareInstanceMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||
@ -63,6 +63,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QSupplementalProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
|
||||
@ -158,7 +159,7 @@ public class QInstanceValidator
|
||||
validateQueuesAndProviders(qInstance);
|
||||
validateJoins(qInstance);
|
||||
validateSecurityKeyTypes(qInstance);
|
||||
validateMiddlewareMetaData(qInstance);
|
||||
validateSupplementalMetaData(qInstance);
|
||||
|
||||
validateUniqueTopLevelNames(qInstance);
|
||||
}
|
||||
@ -182,11 +183,11 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateMiddlewareMetaData(QInstance qInstance)
|
||||
private void validateSupplementalMetaData(QInstance qInstance)
|
||||
{
|
||||
for(QMiddlewareInstanceMetaData middlewareInstanceMetaData : CollectionUtils.nonNullMap(qInstance.getMiddlewareMetaData()).values())
|
||||
for(QSupplementalInstanceMetaData supplementalInstanceMetaData : CollectionUtils.nonNullMap(qInstance.getSupplementalMetaData()).values())
|
||||
{
|
||||
middlewareInstanceMetaData.validate(qInstance, this);
|
||||
supplementalInstanceMetaData.validate(qInstance, this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -572,6 +573,11 @@ public class QInstanceValidator
|
||||
RECORD_SECURITY_LOCKS_LOOP:
|
||||
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(table.getRecordSecurityLocks()))
|
||||
{
|
||||
if(!assertCondition(recordSecurityLock != null, prefix + "has a null recordSecurityLock (did you mean to give it a null list of locks?)"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
String securityKeyTypeName = recordSecurityLock.getSecurityKeyType();
|
||||
if(assertCondition(StringUtils.hasContent(securityKeyTypeName), prefix + "has a recordSecurityLock that is missing a securityKeyType"))
|
||||
{
|
||||
@ -1218,14 +1224,18 @@ public class QInstanceValidator
|
||||
QScheduleMetaData schedule = process.getSchedule();
|
||||
assertCondition(schedule.getRepeatMillis() != null || schedule.getRepeatSeconds() != null, "Either repeat millis or repeat seconds must be set on schedule in process " + processName);
|
||||
|
||||
if(schedule.getBackendVariant() != null)
|
||||
if(schedule.getVariantBackend() != null)
|
||||
{
|
||||
assertCondition(schedule.getVariantRunStrategy() != null, "A variant strategy was not set for " + schedule.getBackendVariant() + " on schedule in process " + processName);
|
||||
assertCondition(schedule.getVariantTableName() != null, "A variant table name was not set for " + schedule.getBackendVariant() + " on schedule in process " + processName);
|
||||
assertCondition(schedule.getVariantFieldName() != null, "A variant field name was not set for " + schedule.getBackendVariant() + " on schedule in process " + processName);
|
||||
assertCondition(qInstance.getBackend(schedule.getVariantBackend()) != null, "A variant backend was not found for " + schedule.getVariantBackend());
|
||||
assertCondition(schedule.getVariantRunStrategy() != null, "A variant run strategy was not set for " + schedule.getVariantBackend() + " on schedule in process " + processName);
|
||||
}
|
||||
}
|
||||
|
||||
for(QSupplementalProcessMetaData supplementalProcessMetaData : CollectionUtils.nonNullMap(process.getSupplementalMetaData()).values())
|
||||
{
|
||||
supplementalProcessMetaData.validate(qInstance, process, this);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1703,7 +1713,7 @@ public class QInstanceValidator
|
||||
** But if it throws, add the provided message to the list of errors (and return false,
|
||||
** e.g., in case you need to stop evaluating rules to avoid exceptions).
|
||||
*******************************************************************************/
|
||||
private boolean assertNoException(UnsafeLambda unsafeLambda, String message)
|
||||
public boolean assertNoException(UnsafeLambda unsafeLambda, String message)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -1736,7 +1746,7 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void warn(String message)
|
||||
public void warn(String message)
|
||||
{
|
||||
if(printWarnings)
|
||||
{
|
||||
|
@ -30,6 +30,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import io.github.cdimascio.dotenv.Dotenv;
|
||||
import io.github.cdimascio.dotenv.DotenvEntry;
|
||||
@ -266,4 +267,111 @@ public class QMetaDataVariableInterpreter
|
||||
|
||||
valueMaps.put(name, values);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** First look for a boolean ("true" or "false") in the specified system property -
|
||||
** Next look for a boolean in the specified env var name -
|
||||
** Finally return the default.
|
||||
*******************************************************************************/
|
||||
public boolean getBooleanFromPropertyOrEnvironment(String systemPropertyName, String environmentVariableName, boolean defaultIfNotSet)
|
||||
{
|
||||
String propertyValue = System.getProperty(systemPropertyName);
|
||||
if(StringUtils.hasContent(propertyValue))
|
||||
{
|
||||
if("false".equalsIgnoreCase(propertyValue))
|
||||
{
|
||||
LOG.info("Read system property [" + systemPropertyName + "] as boolean false.");
|
||||
return (false);
|
||||
}
|
||||
else if("true".equalsIgnoreCase(propertyValue))
|
||||
{
|
||||
LOG.info("Read system property [" + systemPropertyName + "] as boolean true.");
|
||||
return (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Unrecognized boolean value [" + propertyValue + "] for system property [" + systemPropertyName + "].");
|
||||
}
|
||||
}
|
||||
|
||||
String envValue = interpret("${env." + environmentVariableName + "}");
|
||||
if(StringUtils.hasContent(envValue))
|
||||
{
|
||||
if("false".equalsIgnoreCase(envValue))
|
||||
{
|
||||
LOG.info("Read env var [" + environmentVariableName + "] as boolean false.");
|
||||
return (false);
|
||||
}
|
||||
else if("true".equalsIgnoreCase(envValue))
|
||||
{
|
||||
LOG.info("Read env var [" + environmentVariableName + "] as boolean true.");
|
||||
return (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Unrecognized boolean value [" + envValue + "] for env var [" + environmentVariableName + "].");
|
||||
}
|
||||
}
|
||||
|
||||
return defaultIfNotSet;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** First look for an Integer in the specified system property -
|
||||
** Next look for an Integer in the specified env var name -
|
||||
** Finally return the default (null allowed as default!)
|
||||
*******************************************************************************/
|
||||
public Integer getIntegerFromPropertyOrEnvironment(String systemPropertyName, String environmentVariableName, Integer defaultIfNotSet)
|
||||
{
|
||||
String propertyValue = System.getProperty(systemPropertyName);
|
||||
if(StringUtils.hasContent(propertyValue))
|
||||
{
|
||||
if(canParseAsInteger(propertyValue))
|
||||
{
|
||||
LOG.info("Read system property [" + systemPropertyName + "] as integer " + propertyValue);
|
||||
return (Integer.parseInt(propertyValue));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Unrecognized integer value [" + propertyValue + "] for system property [" + systemPropertyName + "].");
|
||||
}
|
||||
}
|
||||
|
||||
String envValue = interpret("${env." + environmentVariableName + "}");
|
||||
if(StringUtils.hasContent(envValue))
|
||||
{
|
||||
if(canParseAsInteger(envValue))
|
||||
{
|
||||
LOG.info("Read env var [" + environmentVariableName + "] as integer " + environmentVariableName);
|
||||
return (Integer.parseInt(propertyValue));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Unrecognized integer value [" + envValue + "] for env var [" + environmentVariableName + "].");
|
||||
}
|
||||
}
|
||||
|
||||
return defaultIfNotSet;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** we'd use NumberUtils.isDigits, but that doesn't allow negatives, or
|
||||
** numberUtils.isParseable, but that allows decimals, so...
|
||||
*******************************************************************************/
|
||||
private boolean canParseAsInteger(String value)
|
||||
{
|
||||
if(value == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (value.matches("^-?[0-9]+$"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,10 +29,12 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@ -392,7 +394,7 @@ public class QLogger
|
||||
*******************************************************************************/
|
||||
public void warn(String message, Throwable t)
|
||||
{
|
||||
logger.warn(makeJsonString(message, t));
|
||||
logger.log(determineIfShouldDowngrade(t, Level.WARN), makeJsonString(message, t));
|
||||
}
|
||||
|
||||
|
||||
@ -402,7 +404,7 @@ public class QLogger
|
||||
*******************************************************************************/
|
||||
public void warn(String message, Throwable t, LogPair... logPairs)
|
||||
{
|
||||
logger.warn(makeJsonString(message, t, logPairs));
|
||||
logger.log(determineIfShouldDowngrade(t, Level.WARN), makeJsonString(message, t, logPairs));
|
||||
}
|
||||
|
||||
|
||||
@ -412,7 +414,7 @@ public class QLogger
|
||||
*******************************************************************************/
|
||||
public void warn(Throwable t)
|
||||
{
|
||||
logger.warn(makeJsonString(null, t));
|
||||
logger.log(determineIfShouldDowngrade(t, Level.WARN), makeJsonString(null, t));
|
||||
}
|
||||
|
||||
|
||||
@ -452,7 +454,7 @@ public class QLogger
|
||||
*******************************************************************************/
|
||||
public void error(String message, Throwable t)
|
||||
{
|
||||
logger.error(makeJsonString(message, t));
|
||||
logger.log(determineIfShouldDowngrade(t, Level.ERROR), makeJsonString(message, t));
|
||||
}
|
||||
|
||||
|
||||
@ -462,7 +464,7 @@ public class QLogger
|
||||
*******************************************************************************/
|
||||
public void error(String message, Throwable t, LogPair... logPairs)
|
||||
{
|
||||
logger.error(makeJsonString(message, t, logPairs));
|
||||
logger.log(determineIfShouldDowngrade(t, Level.ERROR), makeJsonString(message, t, logPairs));
|
||||
}
|
||||
|
||||
|
||||
@ -472,7 +474,7 @@ public class QLogger
|
||||
*******************************************************************************/
|
||||
public void error(Throwable t)
|
||||
{
|
||||
logger.error(makeJsonString(null, t));
|
||||
logger.log(determineIfShouldDowngrade(t, Level.ERROR), makeJsonString(null, t));
|
||||
}
|
||||
|
||||
|
||||
@ -532,7 +534,7 @@ public class QLogger
|
||||
|
||||
if(t != null)
|
||||
{
|
||||
logPairList.add(logPair("stackTrace", LogUtils.filterStackTrace(ExceptionUtils.getStackTrace(t))));
|
||||
logPairList.add(logPair("stackTrace", LogUtils.filterStackTrace(org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace(t))));
|
||||
}
|
||||
|
||||
return (LogUtils.jsonLog(logPairList));
|
||||
@ -582,4 +584,40 @@ public class QLogger
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected Level determineIfShouldDowngrade(Throwable t, Level level)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// look for QExceptions in the chain, if none found, return the log level passed in //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
List<QException> exceptionList = ExceptionUtils.getClassListFromRootChain(t, QException.class);
|
||||
if(CollectionUtils.nullSafeIsEmpty(exceptionList))
|
||||
{
|
||||
return (level);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// check if any QException in this chain to see if it has already //
|
||||
// logged this level, if so, downgrade to INFO //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
for(QException qException : exceptionList)
|
||||
{
|
||||
if(qException.hasLoggedLevel(level))
|
||||
{
|
||||
log(Level.DEBUG, "Downgrading log message from " + level.toString() + " to " + Level.INFO, t);
|
||||
return (Level.INFO);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if it has not logged at this level, set that it has in QException, and return passed in level //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
exceptionList.get(0).setHasLoggedLevel(level);
|
||||
return (level);
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,16 @@ public class AbstractActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getActionIdentity()
|
||||
{
|
||||
return (getClass().getSimpleName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** performance instance validation (if not previously done).
|
||||
* // todo - verify this is happening (e.g., when context is set i guess)
|
||||
@ -145,14 +155,4 @@ public class AbstractActionInput
|
||||
this.asyncJobCallback = asyncJobCallback;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for instance
|
||||
*******************************************************************************/
|
||||
public AbstractActionInput withInstance(QInstance instance)
|
||||
{
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -46,6 +46,17 @@ public class AbstractTableActionInput extends AbstractActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getActionIdentity()
|
||||
{
|
||||
return (getClass().getSimpleName() + ":" + getTableName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -22,8 +22,10 @@
|
||||
package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -76,6 +78,35 @@ public class ProcessSummaryFilterLink implements ProcessSummaryLineInterface
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@JsonIgnore
|
||||
public String getFullText()
|
||||
{
|
||||
StringBuilder rs = new StringBuilder();
|
||||
|
||||
if(StringUtils.hasContent(linkPreText))
|
||||
{
|
||||
rs.append(linkPreText).append(" ");
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(linkText))
|
||||
{
|
||||
rs.append(linkText).append(" ");
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(linkPostText))
|
||||
{
|
||||
rs.append(linkPostText).append(" ");
|
||||
}
|
||||
|
||||
rs.deleteCharAt(rs.length() - 1);
|
||||
return (rs.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -26,6 +26,7 @@ import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
@ -395,15 +396,19 @@ public class ProcessSummaryLine implements ProcessSummaryLineInterface
|
||||
{
|
||||
if(count != null)
|
||||
{
|
||||
String baseMessage;
|
||||
if(count.equals(1))
|
||||
{
|
||||
setMessage((isPast ? getSingularPastMessage() : getSingularFutureMessage())
|
||||
+ (messageSuffix == null ? "" : messageSuffix));
|
||||
baseMessage = isPast ? getSingularPastMessage() : getSingularFutureMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
setMessage((isPast ? getPluralPastMessage() : getPluralFutureMessage())
|
||||
+ (messageSuffix == null ? "" : messageSuffix));
|
||||
baseMessage = isPast ? getPluralPastMessage() : getPluralFutureMessage();
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(baseMessage))
|
||||
{
|
||||
setMessage(baseMessage + ObjectUtils.requireConditionElse(messageSuffix, StringUtils::hasContent, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,9 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -64,6 +66,35 @@ public class ProcessSummaryRecordLink implements ProcessSummaryLineInterface
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@JsonIgnore
|
||||
public String getFullText()
|
||||
{
|
||||
StringBuilder rs = new StringBuilder();
|
||||
|
||||
if(StringUtils.hasContent(linkPreText))
|
||||
{
|
||||
rs.append(linkPreText).append(" ");
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(linkText))
|
||||
{
|
||||
rs.append(linkText).append(" ");
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(linkPostText))
|
||||
{
|
||||
rs.append(linkPostText).append(" ");
|
||||
}
|
||||
|
||||
rs.deleteCharAt(rs.length() - 1);
|
||||
return (rs.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -23,6 +23,10 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
|
||||
@ -31,6 +35,7 @@ import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -71,6 +76,17 @@ public class RunProcessInput extends AbstractActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getActionIdentity()
|
||||
{
|
||||
return (getClass().getSimpleName() + ":" + getProcessName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** e.g., for steps after the first step in a process, seed the data in a run
|
||||
** function request from a process state.
|
||||
@ -183,6 +199,17 @@ public class RunProcessInput extends AbstractActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunProcessInput withValue(String fieldName, Serializable value)
|
||||
{
|
||||
this.processState.getValues().put(fieldName, value);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for values
|
||||
**
|
||||
@ -258,7 +285,7 @@ public class RunProcessInput extends AbstractActionInput
|
||||
*******************************************************************************/
|
||||
public String getValueString(String fieldName)
|
||||
{
|
||||
return ((String) getValue(fieldName));
|
||||
return (ValueUtils.getValueAsString(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
@ -269,7 +296,67 @@ public class RunProcessInput extends AbstractActionInput
|
||||
*******************************************************************************/
|
||||
public Integer getValueInteger(String fieldName)
|
||||
{
|
||||
return ((Integer) getValue(fieldName));
|
||||
return (ValueUtils.getValueAsInteger(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public BigDecimal getValueBigDecimal(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsBigDecimal(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Boolean getValueBoolean(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsBoolean(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public LocalTime getValueLocalTime(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsLocalTime(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public LocalDate getValueLocalDate(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsLocalDate(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public byte[] getValueByteArray(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsByteArray(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Instant getValueInstant(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsInstant(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
@ -23,11 +23,16 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -122,6 +127,99 @@ public class RunProcessOutput extends AbstractActionOutput implements Serializab
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for a single field's value
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Serializable getValue(String fieldName)
|
||||
{
|
||||
return (this.processState.getValues().get(fieldName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for a single field's value
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getValueString(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsString(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for a single field's value
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getValueInteger(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsInteger(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public BigDecimal getValueBigDecimal(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsBigDecimal(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Boolean getValueBoolean(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsBoolean(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public LocalTime getValueLocalTime(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsLocalTime(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public LocalDate getValueLocalDate(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsLocalDate(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public byte[] getValueByteArray(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsByteArray(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Instant getValueInstant(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsInstant(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for values
|
||||
**
|
||||
@ -133,6 +231,17 @@ public class RunProcessOutput extends AbstractActionOutput implements Serializab
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunProcessOutput withValue(String fieldName, Serializable value)
|
||||
{
|
||||
this.processState.getValues().put(fieldName, value);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for values
|
||||
**
|
||||
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Common getters & setters, shared by both QueryInput and GetInput.
|
||||
**
|
||||
** Original impetus for this class is the setCommonParamsFrom() method - for cases
|
||||
** where we need to change a Query to a Get, or vice-versa, and we want to copy over
|
||||
** all of those input params.
|
||||
*******************************************************************************/
|
||||
public interface QueryOrGetInputInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Set in THIS, the "common params" (e.g., common to both Query & Get inputs)
|
||||
** from the parameter SOURCE object.
|
||||
*******************************************************************************/
|
||||
default void setCommonParamsFrom(QueryOrGetInputInterface source)
|
||||
{
|
||||
this.setTransaction(source.getTransaction());
|
||||
this.setShouldTranslatePossibleValues(source.getShouldTranslatePossibleValues());
|
||||
this.setShouldGenerateDisplayValues(source.getShouldGenerateDisplayValues());
|
||||
this.setShouldFetchHeavyFields(source.getShouldFetchHeavyFields());
|
||||
this.setShouldOmitHiddenFields(source.getShouldOmitHiddenFields());
|
||||
this.setShouldMaskPasswords(source.getShouldMaskPasswords());
|
||||
this.setIncludeAssociations(source.getIncludeAssociations());
|
||||
this.setAssociationNamesToInclude(source.getAssociationNamesToInclude());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for transaction
|
||||
*******************************************************************************/
|
||||
QBackendTransaction getTransaction();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for transaction
|
||||
*******************************************************************************/
|
||||
void setTransaction(QBackendTransaction transaction);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shouldTranslatePossibleValues
|
||||
*******************************************************************************/
|
||||
boolean getShouldTranslatePossibleValues();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shouldTranslatePossibleValues
|
||||
*******************************************************************************/
|
||||
void setShouldTranslatePossibleValues(boolean shouldTranslatePossibleValues);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shouldGenerateDisplayValues
|
||||
*******************************************************************************/
|
||||
boolean getShouldGenerateDisplayValues();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shouldGenerateDisplayValues
|
||||
*******************************************************************************/
|
||||
void setShouldGenerateDisplayValues(boolean shouldGenerateDisplayValues);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shouldFetchHeavyFields
|
||||
*******************************************************************************/
|
||||
boolean getShouldFetchHeavyFields();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shouldFetchHeavyFields
|
||||
*******************************************************************************/
|
||||
void setShouldFetchHeavyFields(boolean shouldFetchHeavyFields);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shouldOmitHiddenFields
|
||||
*******************************************************************************/
|
||||
boolean getShouldOmitHiddenFields();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shouldOmitHiddenFields
|
||||
*******************************************************************************/
|
||||
void setShouldOmitHiddenFields(boolean shouldOmitHiddenFields);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shouldMaskPasswords
|
||||
*******************************************************************************/
|
||||
boolean getShouldMaskPasswords();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shouldMaskPasswords
|
||||
*******************************************************************************/
|
||||
void setShouldMaskPasswords(boolean shouldMaskPasswords);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for includeAssociations
|
||||
*******************************************************************************/
|
||||
boolean getIncludeAssociations();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for includeAssociations
|
||||
*******************************************************************************/
|
||||
void setIncludeAssociations(boolean includeAssociations);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for associationNamesToInclude
|
||||
*******************************************************************************/
|
||||
Collection<String> getAssociationNamesToInclude();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for associationNamesToInclude
|
||||
*******************************************************************************/
|
||||
void setAssociationNamesToInclude(Collection<String> associationNamesToInclude);
|
||||
|
||||
}
|
@ -40,6 +40,8 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
private List<GroupBy> groupBys = new ArrayList<>();
|
||||
private Integer limit;
|
||||
|
||||
private Integer timeoutSeconds;
|
||||
|
||||
private List<QueryJoin> queryJoins = null;
|
||||
|
||||
|
||||
@ -269,4 +271,35 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for timeoutSeconds
|
||||
*******************************************************************************/
|
||||
public Integer getTimeoutSeconds()
|
||||
{
|
||||
return (this.timeoutSeconds);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for timeoutSeconds
|
||||
*******************************************************************************/
|
||||
public void setTimeoutSeconds(Integer timeoutSeconds)
|
||||
{
|
||||
this.timeoutSeconds = timeoutSeconds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for timeoutSeconds
|
||||
*******************************************************************************/
|
||||
public AggregateInput withTimeoutSeconds(Integer timeoutSeconds)
|
||||
{
|
||||
this.timeoutSeconds = timeoutSeconds;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -56,4 +56,14 @@ public enum AggregateOperator
|
||||
{
|
||||
return sqlPrefix;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Aggregate of(String fieldName)
|
||||
{
|
||||
return (new Aggregate(fieldName, this));
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ public class CountInput extends AbstractTableActionInput
|
||||
{
|
||||
private QQueryFilter filter;
|
||||
|
||||
private Integer timeoutSeconds;
|
||||
|
||||
private List<QueryJoin> queryJoins = null;
|
||||
private Boolean includeDistinctCount = false;
|
||||
|
||||
@ -51,6 +53,17 @@ public class CountInput extends AbstractTableActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CountInput(String tableName)
|
||||
{
|
||||
setTableName(tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filter
|
||||
**
|
||||
@ -152,4 +165,46 @@ public class CountInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for filter
|
||||
*******************************************************************************/
|
||||
public CountInput withFilter(QQueryFilter filter)
|
||||
{
|
||||
this.filter = filter;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for timeoutSeconds
|
||||
*******************************************************************************/
|
||||
public Integer getTimeoutSeconds()
|
||||
{
|
||||
return (this.timeoutSeconds);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for timeoutSeconds
|
||||
*******************************************************************************/
|
||||
public void setTimeoutSeconds(Integer timeoutSeconds)
|
||||
{
|
||||
this.timeoutSeconds = timeoutSeconds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for timeoutSeconds
|
||||
*******************************************************************************/
|
||||
public CountInput withTimeoutSeconds(Integer timeoutSeconds)
|
||||
{
|
||||
this.timeoutSeconds = timeoutSeconds;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -43,6 +43,9 @@ public class DeleteInput extends AbstractTableActionInput
|
||||
private QQueryFilter queryFilter;
|
||||
private InputSource inputSource = QInputSource.SYSTEM;
|
||||
|
||||
private boolean omitDmlAudit = false;
|
||||
private String auditContext = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -54,6 +57,29 @@ public class DeleteInput extends AbstractTableActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public DeleteInput(String tableName)
|
||||
{
|
||||
setTableName(tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public DeleteInput withTableName(String tableName)
|
||||
{
|
||||
super.withTableName(tableName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for transaction
|
||||
**
|
||||
@ -188,4 +214,66 @@ public class DeleteInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for omitDmlAudit
|
||||
*******************************************************************************/
|
||||
public boolean getOmitDmlAudit()
|
||||
{
|
||||
return (this.omitDmlAudit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for omitDmlAudit
|
||||
*******************************************************************************/
|
||||
public void setOmitDmlAudit(boolean omitDmlAudit)
|
||||
{
|
||||
this.omitDmlAudit = omitDmlAudit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for omitDmlAudit
|
||||
*******************************************************************************/
|
||||
public DeleteInput withOmitDmlAudit(boolean omitDmlAudit)
|
||||
{
|
||||
this.omitDmlAudit = omitDmlAudit;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for auditContext
|
||||
*******************************************************************************/
|
||||
public String getAuditContext()
|
||||
{
|
||||
return (this.auditContext);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for auditContext
|
||||
*******************************************************************************/
|
||||
public void setAuditContext(String auditContext)
|
||||
{
|
||||
this.auditContext = auditContext;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for auditContext
|
||||
*******************************************************************************/
|
||||
public DeleteInput withAuditContext(String auditContext)
|
||||
{
|
||||
this.auditContext = auditContext;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,13 +27,14 @@ import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Input data for the Get action
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class GetInput extends AbstractTableActionInput
|
||||
public class GetInput extends AbstractTableActionInput implements QueryOrGetInputInterface
|
||||
{
|
||||
private QBackendTransaction transaction;
|
||||
|
||||
@ -66,6 +67,29 @@ public class GetInput extends AbstractTableActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public GetInput(String tableName)
|
||||
{
|
||||
setTableName(tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public AbstractTableActionInput withTableName(String tableName)
|
||||
{
|
||||
super.withTableName(tableName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for primaryKey
|
||||
**
|
||||
|
@ -22,12 +22,15 @@
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables.insert;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -43,6 +46,7 @@ public class InsertInput extends AbstractTableActionInput
|
||||
private boolean skipUniqueKeyCheck = false;
|
||||
|
||||
private boolean omitDmlAudit = false;
|
||||
private String auditContext = null;
|
||||
|
||||
|
||||
|
||||
@ -55,6 +59,71 @@ public class InsertInput extends AbstractTableActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public InsertInput(String tableName)
|
||||
{
|
||||
setTableName(tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public InsertInput withTableName(String tableName)
|
||||
{
|
||||
super.withTableName(tableName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public InsertInput withRecord(QRecord record)
|
||||
{
|
||||
if(records == null)
|
||||
{
|
||||
records = new ArrayList<>();
|
||||
}
|
||||
|
||||
records.add(record);
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public InsertInput withRecordEntity(QRecordEntity recordEntity)
|
||||
{
|
||||
return (withRecord(recordEntity.toQRecord()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public InsertInput withRecordEntities(List<QRecordEntity> recordEntityList)
|
||||
{
|
||||
for(QRecordEntity recordEntity : CollectionUtils.nonNullList(recordEntityList))
|
||||
{
|
||||
withRecordEntity(recordEntity);
|
||||
}
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for transaction
|
||||
**
|
||||
@ -216,4 +285,35 @@ public class InsertInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for auditContext
|
||||
*******************************************************************************/
|
||||
public String getAuditContext()
|
||||
{
|
||||
return (this.auditContext);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for auditContext
|
||||
*******************************************************************************/
|
||||
public void setAuditContext(String auditContext)
|
||||
{
|
||||
this.auditContext = auditContext;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for auditContext
|
||||
*******************************************************************************/
|
||||
public InsertInput withAuditContext(String auditContext)
|
||||
{
|
||||
this.auditContext = auditContext;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,8 +26,9 @@ import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.serialization.QFilterCriteriaDeserializer;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
@ -36,6 +37,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
* A single criteria Component of a Query
|
||||
*
|
||||
*******************************************************************************/
|
||||
@JsonDeserialize(using = QFilterCriteriaDeserializer.class)
|
||||
public class QFilterCriteria implements Serializable, Cloneable
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QFilterCriteria.class);
|
||||
@ -44,8 +46,10 @@ public class QFilterCriteria implements Serializable, Cloneable
|
||||
private QCriteriaOperator operator;
|
||||
private List<Serializable> values;
|
||||
|
||||
private String otherFieldName;
|
||||
private AbstractFilterExpression<?> expression;
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - probably implement this as a type of expression - though would require a little special handling i think when evaluating... //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private String otherFieldName;
|
||||
|
||||
|
||||
|
||||
@ -98,23 +102,6 @@ public class QFilterCriteria implements Serializable, Cloneable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFilterCriteria(String fieldName, QCriteriaOperator operator, AbstractFilterExpression<?> expression)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
this.operator = operator;
|
||||
this.expression = expression;
|
||||
|
||||
///////////////////////////////////////
|
||||
// this guy doesn't like to be null? //
|
||||
///////////////////////////////////////
|
||||
this.values = new ArrayList<>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -332,35 +319,4 @@ public class QFilterCriteria implements Serializable, Cloneable
|
||||
return (rs.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for expression
|
||||
*******************************************************************************/
|
||||
public AbstractFilterExpression<?> getExpression()
|
||||
{
|
||||
return (this.expression);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for expression
|
||||
*******************************************************************************/
|
||||
public void setExpression(AbstractFilterExpression<?> expression)
|
||||
{
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for expression
|
||||
*******************************************************************************/
|
||||
public QFilterCriteria withExpression(AbstractFilterExpression<?> expression)
|
||||
{
|
||||
this.expression = expression;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,18 +29,20 @@ import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Input data for the Query action
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QueryInput extends AbstractTableActionInput
|
||||
public class QueryInput extends AbstractTableActionInput implements QueryOrGetInputInterface
|
||||
{
|
||||
private QBackendTransaction transaction;
|
||||
private QQueryFilter filter;
|
||||
|
||||
private RecordPipe recordPipe;
|
||||
private Integer timeoutSeconds;
|
||||
|
||||
private boolean shouldTranslatePossibleValues = false;
|
||||
private boolean shouldGenerateDisplayValues = false;
|
||||
@ -77,6 +79,17 @@ public class QueryInput extends AbstractTableActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QueryInput(String tableName)
|
||||
{
|
||||
setTableName(tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filter
|
||||
**
|
||||
@ -525,4 +538,35 @@ public class QueryInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for timeoutSeconds
|
||||
*******************************************************************************/
|
||||
public Integer getTimeoutSeconds()
|
||||
{
|
||||
return (this.timeoutSeconds);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for timeoutSeconds
|
||||
*******************************************************************************/
|
||||
public void setTimeoutSeconds(Integer timeoutSeconds)
|
||||
{
|
||||
this.timeoutSeconds = timeoutSeconds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for timeoutSeconds
|
||||
*******************************************************************************/
|
||||
public QueryInput withTimeoutSeconds(Integer timeoutSeconds)
|
||||
{
|
||||
this.timeoutSeconds = timeoutSeconds;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,10 +28,31 @@ import java.io.Serializable;
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractFilterExpression<T extends Serializable>
|
||||
public abstract class AbstractFilterExpression<T extends Serializable> implements Serializable
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract T evaluate();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** To help with serialization, define a "type" in all subclasses
|
||||
*******************************************************************************/
|
||||
public String getType()
|
||||
{
|
||||
return (getClass().getSimpleName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** noop - but here so serialization won't be upset about there being a type
|
||||
** in a json object.
|
||||
*******************************************************************************/
|
||||
public void setType(String type)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,10 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
@ -31,9 +35,9 @@ import java.util.concurrent.TimeUnit;
|
||||
*******************************************************************************/
|
||||
public class NowWithOffset extends AbstractFilterExpression<Instant>
|
||||
{
|
||||
private final Operator operator;
|
||||
private final int amount;
|
||||
private final TimeUnit timeUnit;
|
||||
private Operator operator;
|
||||
private int amount;
|
||||
private ChronoUnit timeUnit;
|
||||
|
||||
|
||||
|
||||
@ -46,7 +50,17 @@ public class NowWithOffset extends AbstractFilterExpression<Instant>
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
private NowWithOffset(Operator operator, int amount, TimeUnit timeUnit)
|
||||
public NowWithOffset()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
private NowWithOffset(Operator operator, int amount, ChronoUnit timeUnit)
|
||||
{
|
||||
this.operator = operator;
|
||||
this.amount = amount;
|
||||
@ -59,7 +73,19 @@ public class NowWithOffset extends AbstractFilterExpression<Instant>
|
||||
** Factory
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Deprecated
|
||||
public static NowWithOffset minus(int amount, TimeUnit timeUnit)
|
||||
{
|
||||
return (minus(amount, timeUnit.toChronoUnit()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Factory
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static NowWithOffset minus(int amount, ChronoUnit timeUnit)
|
||||
{
|
||||
return (new NowWithOffset(Operator.MINUS, amount, timeUnit));
|
||||
}
|
||||
@ -70,7 +96,19 @@ public class NowWithOffset extends AbstractFilterExpression<Instant>
|
||||
** Factory
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Deprecated
|
||||
public static NowWithOffset plus(int amount, TimeUnit timeUnit)
|
||||
{
|
||||
return (plus(amount, timeUnit.toChronoUnit()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Factory
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static NowWithOffset plus(int amount, ChronoUnit timeUnit)
|
||||
{
|
||||
return (new NowWithOffset(Operator.PLUS, amount, timeUnit));
|
||||
}
|
||||
@ -83,14 +121,24 @@ public class NowWithOffset extends AbstractFilterExpression<Instant>
|
||||
@Override
|
||||
public Instant evaluate()
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Instant doesn't let us plus/minus WEEK, MONTH, or YEAR... //
|
||||
// but LocalDateTime does. So, make a LDT in UTC, do the plus/minus, then //
|
||||
// convert back to Instant @ UTC //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
|
||||
|
||||
LocalDateTime then;
|
||||
if(operator.equals(Operator.PLUS))
|
||||
{
|
||||
return (Instant.now().plus(amount, timeUnit.toChronoUnit()));
|
||||
then = now.plus(amount, timeUnit);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (Instant.now().minus(amount, timeUnit.toChronoUnit()));
|
||||
then = now.minus(amount, timeUnit);
|
||||
}
|
||||
|
||||
return (then.toInstant(ZoneOffset.UTC));
|
||||
}
|
||||
|
||||
|
||||
@ -121,7 +169,7 @@ public class NowWithOffset extends AbstractFilterExpression<Instant>
|
||||
** Getter for timeUnit
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TimeUnit getTimeUnit()
|
||||
public ChronoUnit getTimeUnit()
|
||||
{
|
||||
return timeUnit;
|
||||
}
|
||||
|
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions;
|
||||
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ThisOrLastPeriod extends AbstractFilterExpression<Instant>
|
||||
{
|
||||
private Operator operator;
|
||||
private ChronoUnit timeUnit;
|
||||
|
||||
|
||||
|
||||
public enum Operator
|
||||
{THIS, LAST}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ThisOrLastPeriod()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
private ThisOrLastPeriod(Operator operator, ChronoUnit timeUnit)
|
||||
{
|
||||
this.operator = operator;
|
||||
this.timeUnit = timeUnit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Factory
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static ThisOrLastPeriod this_(ChronoUnit timeUnit)
|
||||
{
|
||||
return (new ThisOrLastPeriod(Operator.THIS, timeUnit));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Factory
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static ThisOrLastPeriod last(int amount, ChronoUnit timeUnit)
|
||||
{
|
||||
return (new ThisOrLastPeriod(Operator.LAST, timeUnit));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Instant evaluate()
|
||||
{
|
||||
ZoneId zoneId = ValueUtils.getSessionOrInstanceZoneId();
|
||||
|
||||
switch(timeUnit)
|
||||
{
|
||||
case HOURS ->
|
||||
{
|
||||
if(operator.equals(Operator.THIS))
|
||||
{
|
||||
return Instant.now().truncatedTo(ChronoUnit.HOURS);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Instant.now().minus(1, ChronoUnit.HOURS).truncatedTo(ChronoUnit.HOURS);
|
||||
}
|
||||
}
|
||||
case DAYS ->
|
||||
{
|
||||
Instant startOfToday = ValueUtils.getStartOfTodayInZoneId(zoneId.getId());
|
||||
return operator.equals(Operator.THIS) ? startOfToday : startOfToday.minus(1, ChronoUnit.DAYS);
|
||||
}
|
||||
case WEEKS ->
|
||||
{
|
||||
Instant startOfToday = ValueUtils.getStartOfTodayInZoneId(zoneId.getId());
|
||||
LocalDateTime startOfThisWeekLDT = LocalDateTime.ofInstant(startOfToday, zoneId);
|
||||
while(startOfThisWeekLDT.get(ChronoField.DAY_OF_WEEK) != DayOfWeek.SUNDAY.getValue())
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// go backwards until sunday is found //
|
||||
////////////////////////////////////////
|
||||
startOfThisWeekLDT = startOfThisWeekLDT.minus(1, ChronoUnit.DAYS);
|
||||
}
|
||||
Instant startOfThisWeek = startOfThisWeekLDT.toInstant(zoneId.getRules().getOffset(startOfThisWeekLDT));
|
||||
|
||||
return operator.equals(Operator.THIS) ? startOfThisWeek : startOfThisWeek.minus(7, ChronoUnit.DAYS);
|
||||
}
|
||||
case MONTHS ->
|
||||
{
|
||||
Instant startOfThisMonth = ValueUtils.getStartOfMonthInZoneId(zoneId.getId());
|
||||
LocalDateTime startOfThisMonthLDT = LocalDateTime.ofInstant(startOfThisMonth, ZoneId.of(zoneId.getId()));
|
||||
LocalDateTime startOfLastMonthLDT = startOfThisMonthLDT.minus(1, ChronoUnit.MONTHS);
|
||||
Instant startOfLastMonth = startOfLastMonthLDT.toInstant(ZoneId.of(zoneId.getId()).getRules().getOffset(Instant.now()));
|
||||
|
||||
return operator.equals(Operator.THIS) ? startOfThisMonth : startOfLastMonth;
|
||||
}
|
||||
case YEARS ->
|
||||
{
|
||||
Instant startOfThisYear = ValueUtils.getStartOfYearInZoneId(zoneId.getId());
|
||||
LocalDateTime startOfThisYearLDT = LocalDateTime.ofInstant(startOfThisYear, zoneId);
|
||||
LocalDateTime startOfLastYearLDT = startOfThisYearLDT.minus(1, ChronoUnit.YEARS);
|
||||
Instant startOfLastYear = startOfLastYearLDT.toInstant(zoneId.getRules().getOffset(Instant.now()));
|
||||
|
||||
return operator.equals(Operator.THIS) ? startOfThisYear : startOfLastYear;
|
||||
}
|
||||
default -> throw (new QRuntimeException("Unsupported timeUnit: " + timeUnit));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for operator
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Operator getOperator()
|
||||
{
|
||||
return operator;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for timeUnit
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ChronoUnit getTimeUnit()
|
||||
{
|
||||
return timeUnit;
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables.query.serialization;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import com.fasterxml.jackson.core.JacksonException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
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.expressions.AbstractFilterExpression;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Custom jackson deserializer, to deal w/ abstract expression field
|
||||
*******************************************************************************/
|
||||
public class QFilterCriteriaDeserializer extends StdDeserializer<QFilterCriteria>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFilterCriteriaDeserializer()
|
||||
{
|
||||
this(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFilterCriteriaDeserializer(Class<?> vc)
|
||||
{
|
||||
super(vc);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QFilterCriteria deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException
|
||||
{
|
||||
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/////////////////////////////////
|
||||
// get values out of json node //
|
||||
/////////////////////////////////
|
||||
List<Serializable> values = objectMapper.treeToValue(node.get("values"), List.class);
|
||||
String fieldName = objectMapper.treeToValue(node.get("fieldName"), String.class);
|
||||
QCriteriaOperator operator = objectMapper.treeToValue(node.get("operator"), QCriteriaOperator.class);
|
||||
String otherFieldName = objectMapper.treeToValue(node.get("otherFieldName"), String.class);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// look at all the values - if any of them are actually meant to be an Expression (instance of subclass of AbstractFilterExpression) //
|
||||
// they'll have deserialized as a Map, with a "type" key. If that's the case, then re/de serialize them into the proper expression type //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
ListIterator<Serializable> valuesIterator = CollectionUtils.nonNullList(values).listIterator();
|
||||
while(valuesIterator.hasNext())
|
||||
{
|
||||
Object value = valuesIterator.next();
|
||||
if(value instanceof Map<?, ?> map && map.containsKey("type"))
|
||||
{
|
||||
String expressionType = ValueUtils.getValueAsString(map.get("type"));
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// right now, we'll assume that all expression subclasses are in the same package as AbstractFilterExpression //
|
||||
// so, we can just do a Class.forName on that name, and use JsonUtils.toObject requesting that class. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
try
|
||||
{
|
||||
String assumedExpressionJSON = JsonUtils.toJson(map);
|
||||
|
||||
String className = AbstractFilterExpression.class.getName().replace(AbstractFilterExpression.class.getSimpleName(), expressionType);
|
||||
Serializable replacementValue = (Serializable) JsonUtils.toObject(assumedExpressionJSON, Class.forName(className));
|
||||
valuesIterator.set(replacementValue);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new IOException("Error deserializing criteria value which appeared to be an expression of type [" + expressionType + "] inside QFilterCriteria", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
// put fields into return object //
|
||||
///////////////////////////////////
|
||||
QFilterCriteria criteria = new QFilterCriteria();
|
||||
criteria.setFieldName(fieldName);
|
||||
criteria.setOperator(operator);
|
||||
criteria.setValues(values);
|
||||
criteria.setOtherFieldName(otherFieldName);
|
||||
|
||||
return (criteria);
|
||||
}
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables.replace;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ReplaceInput extends AbstractTableActionInput
|
||||
{
|
||||
private QBackendTransaction transaction;
|
||||
private UniqueKey key;
|
||||
private List<QRecord> records;
|
||||
private QQueryFilter filter;
|
||||
|
||||
private boolean omitDmlAudit = false;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ReplaceInput()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for transaction
|
||||
*******************************************************************************/
|
||||
public QBackendTransaction getTransaction()
|
||||
{
|
||||
return (this.transaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for transaction
|
||||
*******************************************************************************/
|
||||
public void setTransaction(QBackendTransaction transaction)
|
||||
{
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for transaction
|
||||
*******************************************************************************/
|
||||
public ReplaceInput withTransaction(QBackendTransaction transaction)
|
||||
{
|
||||
this.transaction = transaction;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for records
|
||||
*******************************************************************************/
|
||||
public List<QRecord> getRecords()
|
||||
{
|
||||
return (this.records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for records
|
||||
*******************************************************************************/
|
||||
public void setRecords(List<QRecord> records)
|
||||
{
|
||||
this.records = records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for records
|
||||
*******************************************************************************/
|
||||
public ReplaceInput withRecords(List<QRecord> records)
|
||||
{
|
||||
this.records = records;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filter
|
||||
*******************************************************************************/
|
||||
public QQueryFilter getFilter()
|
||||
{
|
||||
return (this.filter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for filter
|
||||
*******************************************************************************/
|
||||
public void setFilter(QQueryFilter filter)
|
||||
{
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for filter
|
||||
*******************************************************************************/
|
||||
public ReplaceInput withFilter(QQueryFilter filter)
|
||||
{
|
||||
this.filter = filter;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for key
|
||||
*******************************************************************************/
|
||||
public UniqueKey getKey()
|
||||
{
|
||||
return (this.key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for key
|
||||
*******************************************************************************/
|
||||
public void setKey(UniqueKey key)
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for key
|
||||
*******************************************************************************/
|
||||
public ReplaceInput withKey(UniqueKey key)
|
||||
{
|
||||
this.key = key;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for omitDmlAudit
|
||||
*******************************************************************************/
|
||||
public boolean getOmitDmlAudit()
|
||||
{
|
||||
return (this.omitDmlAudit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for omitDmlAudit
|
||||
*******************************************************************************/
|
||||
public void setOmitDmlAudit(boolean omitDmlAudit)
|
||||
{
|
||||
this.omitDmlAudit = omitDmlAudit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for omitDmlAudit
|
||||
*******************************************************************************/
|
||||
public ReplaceInput withOmitDmlAudit(boolean omitDmlAudit)
|
||||
{
|
||||
this.omitDmlAudit = omitDmlAudit;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables.replace;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ReplaceOutput extends AbstractActionOutput
|
||||
{
|
||||
private InsertOutput insertOutput;
|
||||
private UpdateOutput updateOutput;
|
||||
private DeleteOutput deleteOutput;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for insertOutput
|
||||
*******************************************************************************/
|
||||
public InsertOutput getInsertOutput()
|
||||
{
|
||||
return (this.insertOutput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for insertOutput
|
||||
*******************************************************************************/
|
||||
public void setInsertOutput(InsertOutput insertOutput)
|
||||
{
|
||||
this.insertOutput = insertOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for insertOutput
|
||||
*******************************************************************************/
|
||||
public ReplaceOutput withInsertOutput(InsertOutput insertOutput)
|
||||
{
|
||||
this.insertOutput = insertOutput;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for updateOutput
|
||||
*******************************************************************************/
|
||||
public UpdateOutput getUpdateOutput()
|
||||
{
|
||||
return (this.updateOutput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for updateOutput
|
||||
*******************************************************************************/
|
||||
public void setUpdateOutput(UpdateOutput updateOutput)
|
||||
{
|
||||
this.updateOutput = updateOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for updateOutput
|
||||
*******************************************************************************/
|
||||
public ReplaceOutput withUpdateOutput(UpdateOutput updateOutput)
|
||||
{
|
||||
this.updateOutput = updateOutput;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for deleteOutput
|
||||
*******************************************************************************/
|
||||
public DeleteOutput getDeleteOutput()
|
||||
{
|
||||
return (this.deleteOutput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for deleteOutput
|
||||
*******************************************************************************/
|
||||
public void setDeleteOutput(DeleteOutput deleteOutput)
|
||||
{
|
||||
this.deleteOutput = deleteOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for deleteOutput
|
||||
*******************************************************************************/
|
||||
public ReplaceOutput withDeleteOutput(DeleteOutput deleteOutput)
|
||||
{
|
||||
this.deleteOutput = deleteOutput;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -22,12 +22,15 @@
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables.update;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -62,6 +65,71 @@ public class UpdateInput extends AbstractTableActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public UpdateInput(String tableName)
|
||||
{
|
||||
setTableName(tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public UpdateInput withTableName(String tableName)
|
||||
{
|
||||
super.withTableName(tableName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public UpdateInput withRecord(QRecord record)
|
||||
{
|
||||
if(records == null)
|
||||
{
|
||||
records = new ArrayList<>();
|
||||
}
|
||||
|
||||
records.add(record);
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public UpdateInput withRecordEntity(QRecordEntity recordEntity)
|
||||
{
|
||||
return (withRecord(recordEntity.toQRecord()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public UpdateInput withRecordEntities(List<QRecordEntity> recordEntityList)
|
||||
{
|
||||
for(QRecordEntity recordEntity : CollectionUtils.nonNullList(recordEntityList))
|
||||
{
|
||||
withRecordEntity(recordEntity);
|
||||
}
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for transaction
|
||||
**
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.templates;
|
||||
package com.kingsrook.qqq.backend.core.model.actions.templates;
|
||||
|
||||
|
||||
import java.io.OutputStream;
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.templates;
|
||||
package com.kingsrook.qqq.backend.core.model.actions.templates;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
@ -19,11 +19,12 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.templates;
|
||||
package com.kingsrook.qqq.backend.core.model.actions.templates;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.templates.TemplateType;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -35,7 +36,7 @@ public class RenderTemplateInput extends AbstractActionInput
|
||||
private String code; // todo - TemplateReference, like CodeReference??
|
||||
private TemplateType templateType;
|
||||
|
||||
private Map<String, Object> context;
|
||||
private Map<String, ? extends Object> context;
|
||||
|
||||
|
||||
|
||||
@ -120,7 +121,7 @@ public class RenderTemplateInput extends AbstractActionInput
|
||||
** Getter for context
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, Object> getContext()
|
||||
public Map<String, ? extends Object> getContext()
|
||||
{
|
||||
return context;
|
||||
}
|
||||
@ -131,7 +132,7 @@ public class RenderTemplateInput extends AbstractActionInput
|
||||
** Setter for context
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setContext(Map<String, Object> context)
|
||||
public void setContext(Map<String, ? extends Object> context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.templates;
|
||||
package com.kingsrook.qqq.backend.core.model.actions.templates;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
@ -50,6 +50,17 @@ public class RenderWidgetInput extends AbstractActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getActionIdentity()
|
||||
{
|
||||
return (getClass().getSimpleName() + ":" + widgetMetaData.getName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for widgetMetaData
|
||||
**
|
||||
|
@ -39,6 +39,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTable;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -46,7 +47,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
*******************************************************************************/
|
||||
public class AuditsMetaDataProvider
|
||||
{
|
||||
public static final String TABLE_NAME_AUDIT_TABLE = "auditTable";
|
||||
public static final String TABLE_NAME_AUDIT_USER = "auditUser";
|
||||
public static final String TABLE_NAME_AUDIT = "audit";
|
||||
public static final String TABLE_NAME_AUDIT_DETAIL = "auditDetail";
|
||||
@ -72,10 +72,10 @@ public class AuditsMetaDataProvider
|
||||
{
|
||||
instance.addJoin(new QJoinMetaData()
|
||||
.withLeftTable(TABLE_NAME_AUDIT)
|
||||
.withRightTable(TABLE_NAME_AUDIT_TABLE)
|
||||
.withRightTable(QQQTable.TABLE_NAME)
|
||||
.withInferredName()
|
||||
.withType(JoinType.MANY_TO_ONE)
|
||||
.withJoinOn(new JoinOn("auditTableId", "id")));
|
||||
.withJoinOn(new JoinOn("tableId", "id")));
|
||||
|
||||
instance.addJoin(new QJoinMetaData()
|
||||
.withLeftTable(TABLE_NAME_AUDIT)
|
||||
@ -113,11 +113,6 @@ public class AuditsMetaDataProvider
|
||||
*******************************************************************************/
|
||||
public void defineStandardAuditPossibleValueSources(QInstance instance)
|
||||
{
|
||||
instance.addPossibleValueSource(new QPossibleValueSource()
|
||||
.withName(TABLE_NAME_AUDIT_TABLE)
|
||||
.withTableName(TABLE_NAME_AUDIT_TABLE)
|
||||
);
|
||||
|
||||
instance.addPossibleValueSource(new QPossibleValueSource()
|
||||
.withName(TABLE_NAME_AUDIT_USER)
|
||||
.withTableName(TABLE_NAME_AUDIT_USER)
|
||||
@ -138,7 +133,6 @@ public class AuditsMetaDataProvider
|
||||
{
|
||||
List<QTableMetaData> rs = new ArrayList<>();
|
||||
rs.add(enrich(backendDetailEnricher, defineAuditUserTable(backendName)));
|
||||
rs.add(enrich(backendDetailEnricher, defineAuditTableTable(backendName)));
|
||||
rs.add(enrich(backendDetailEnricher, defineAuditTable(backendName)));
|
||||
rs.add(enrich(backendDetailEnricher, defineAuditDetailTable(backendName)));
|
||||
return (rs);
|
||||
@ -160,29 +154,6 @@ public class AuditsMetaDataProvider
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QTableMetaData defineAuditTableTable(String backendName)
|
||||
{
|
||||
return new QTableMetaData()
|
||||
.withName(TABLE_NAME_AUDIT_TABLE)
|
||||
.withBackendName(backendName)
|
||||
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE))
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("label")
|
||||
.withPrimaryKeyField("id")
|
||||
.withUniqueKey(new UniqueKey("name"))
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("label", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME))
|
||||
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME))
|
||||
.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -215,10 +186,10 @@ public class AuditsMetaDataProvider
|
||||
.withBackendName(backendName)
|
||||
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE))
|
||||
.withRecordLabelFormat("%s %s")
|
||||
.withRecordLabelFields("auditTableId", "recordId")
|
||||
.withRecordLabelFields("tableId", "recordId")
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("auditTableId", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_AUDIT_TABLE))
|
||||
.withField(new QFieldMetaData("tableId", QFieldType.INTEGER).withPossibleValueSourceName(QQQTable.TABLE_NAME))
|
||||
.withField(new QFieldMetaData("auditUserId", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_AUDIT_USER))
|
||||
.withField(new QFieldMetaData("recordId", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("message", QFieldType.STRING).withMaxLength(250).withBehavior(ValueTooLongBehavior.TRUNCATE_ELLIPSIS))
|
||||
|
@ -27,8 +27,9 @@ 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;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.TablesPossibleValueSourceMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTable;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -47,10 +48,10 @@ public class TableTrigger extends QRecordEntity
|
||||
@QField(isEditable = false)
|
||||
private Instant modifyDate;
|
||||
|
||||
@QField(possibleValueSourceName = TablesPossibleValueSourceMetaDataProvider.NAME)
|
||||
private String tableName;
|
||||
@QField(possibleValueSourceName = QQQTable.TABLE_NAME, backendName = "qqq_table_id")
|
||||
private Integer tableId;
|
||||
|
||||
@QField(/* todo possibleValueSourceName = */)
|
||||
@QField(possibleValueSourceName = SavedFilter.TABLE_NAME)
|
||||
private Integer filterId;
|
||||
|
||||
@QField(possibleValueSourceName = Script.TABLE_NAME)
|
||||
@ -190,40 +191,6 @@ public class TableTrigger extends QRecordEntity
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableTrigger withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filterId
|
||||
**
|
||||
@ -389,4 +356,35 @@ public class TableTrigger extends QRecordEntity
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableId
|
||||
*******************************************************************************/
|
||||
public Integer getTableId()
|
||||
{
|
||||
return (this.tableId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableId
|
||||
*******************************************************************************/
|
||||
public void setTableId(Integer tableId)
|
||||
{
|
||||
this.tableId = tableId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableId
|
||||
*******************************************************************************/
|
||||
public TableTrigger withTableId(Integer tableId)
|
||||
{
|
||||
this.tableId = tableId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Annotation to place onto fields in a QRecordEntity, to mark them as associated
|
||||
** record lists
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface QAssociation
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String name();
|
||||
|
||||
}
|
@ -23,8 +23,12 @@ package com.kingsrook.qqq.backend.core.model.data;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedParameterizedType;
|
||||
import java.lang.reflect.AnnotatedType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
@ -40,7 +44,9 @@ import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -50,7 +56,8 @@ public abstract class QRecordEntity
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QRecordEntity.class);
|
||||
|
||||
private static final ListingHash<Class<? extends QRecordEntity>, QRecordEntityField> fieldMapping = new ListingHash<>();
|
||||
private static final ListingHash<Class<? extends QRecordEntity>, QRecordEntityField> fieldMapping = new ListingHash<>();
|
||||
private static final ListingHash<Class<? extends QRecordEntity>, QRecordEntityAssociation> associationMapping = new ListingHash<>();
|
||||
|
||||
private Map<String, Serializable> originalRecordValues;
|
||||
|
||||
@ -61,11 +68,23 @@ public abstract class QRecordEntity
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static <T extends QRecordEntity> T fromQRecord(Class<T> c, QRecord qRecord) throws QException
|
||||
{
|
||||
return (QRecordEntity.fromQRecord(c, qRecord, ""));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Build an entity of this QRecord type from a QRecord - where the fields for
|
||||
** this entity have the given prefix - e.g., if they were selected as part of a join.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static <T extends QRecordEntity> T fromQRecord(Class<T> c, QRecord qRecord, String fieldNamePrefix) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
T entity = c.getConstructor().newInstance();
|
||||
entity.populateFromQRecord(qRecord);
|
||||
entity.populateFromQRecord(qRecord, fieldNamePrefix);
|
||||
return (entity);
|
||||
}
|
||||
catch(Exception e)
|
||||
@ -80,19 +99,73 @@ public abstract class QRecordEntity
|
||||
** Build an entity of this QRecord type from a QRecord
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected <T extends QRecordEntity> void populateFromQRecord(QRecord qRecord) throws QRuntimeException
|
||||
protected void populateFromQRecord(QRecord qRecord) throws QRuntimeException
|
||||
{
|
||||
populateFromQRecord(qRecord, "");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Build an entity of this QRecord type from a QRecord - where the fields for
|
||||
** this entity have the given prefix - e.g., if they were selected as part of a join.
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected <T extends QRecordEntity> void populateFromQRecord(QRecord qRecord, String fieldNamePrefix) throws QRuntimeException
|
||||
{
|
||||
try
|
||||
{
|
||||
List<QRecordEntityField> fieldList = getFieldList(this.getClass());
|
||||
originalRecordValues = new HashMap<>();
|
||||
|
||||
if(fieldNamePrefix == null)
|
||||
{
|
||||
fieldNamePrefix = "";
|
||||
}
|
||||
|
||||
for(QRecordEntityField qRecordEntityField : fieldList)
|
||||
{
|
||||
Serializable value = qRecord.getValue(qRecordEntityField.getFieldName());
|
||||
Serializable value = qRecord.getValue(fieldNamePrefix + qRecordEntityField.getFieldName());
|
||||
Object typedValue = qRecordEntityField.convertValueType(value);
|
||||
qRecordEntityField.getSetter().invoke(this, typedValue);
|
||||
originalRecordValues.put(qRecordEntityField.getFieldName(), value);
|
||||
}
|
||||
|
||||
for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
|
||||
{
|
||||
List<QRecord> associatedRecords = qRecord.getAssociatedRecords().get(qRecordEntityAssociation.getAssociationAnnotation().name());
|
||||
if(associatedRecords == null)
|
||||
{
|
||||
qRecordEntityAssociation.getSetter().invoke(this, (Object) null);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<QRecordEntity> associatedEntityList = new ArrayList<>();
|
||||
for(QRecord associatedRecord : CollectionUtils.nonNullList(associatedRecords))
|
||||
{
|
||||
associatedEntityList.add(QRecordEntity.fromQRecord(qRecordEntityAssociation.getAssociatedType(), associatedRecord));
|
||||
}
|
||||
qRecordEntityAssociation.getSetter().invoke(this, associatedEntityList);
|
||||
}
|
||||
}
|
||||
|
||||
for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
|
||||
{
|
||||
List<QRecord> associatedRecords = qRecord.getAssociatedRecords().get(qRecordEntityAssociation.getAssociationAnnotation().name());
|
||||
if(associatedRecords == null)
|
||||
{
|
||||
qRecordEntityAssociation.getSetter().invoke(this, (Object) null);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<QRecordEntity> associatedEntityList = new ArrayList<>();
|
||||
for(QRecord associatedRecord : CollectionUtils.nonNullList(associatedRecords))
|
||||
{
|
||||
associatedEntityList.add(QRecordEntity.fromQRecord(qRecordEntityAssociation.getAssociatedType(), associatedRecord));
|
||||
}
|
||||
qRecordEntityAssociation.getSetter().invoke(this, associatedEntityList);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -112,12 +185,30 @@ public abstract class QRecordEntity
|
||||
{
|
||||
QRecord qRecord = new QRecord();
|
||||
|
||||
List<QRecordEntityField> fieldList = getFieldList(this.getClass());
|
||||
for(QRecordEntityField qRecordEntityField : fieldList)
|
||||
for(QRecordEntityField qRecordEntityField : getFieldList(this.getClass()))
|
||||
{
|
||||
qRecord.setValue(qRecordEntityField.getFieldName(), (Serializable) qRecordEntityField.getGetter().invoke(this));
|
||||
}
|
||||
|
||||
for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
|
||||
{
|
||||
List<? extends QRecordEntity> associatedEntities = (List<? extends QRecordEntity>) qRecordEntityAssociation.getGetter().invoke(this);
|
||||
String associationName = qRecordEntityAssociation.getAssociationAnnotation().name();
|
||||
|
||||
if(associatedEntities != null)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// do this so an empty list in the entity becomes an empty list in the QRecord //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
qRecord.withAssociatedRecords(associationName, new ArrayList<>());
|
||||
}
|
||||
|
||||
for(QRecordEntity associatedEntity : CollectionUtils.nonNullList(associatedEntities))
|
||||
{
|
||||
qRecord.withAssociatedRecord(associationName, associatedEntity.toQRecord());
|
||||
}
|
||||
}
|
||||
|
||||
return (qRecord);
|
||||
}
|
||||
catch(Exception e)
|
||||
@ -127,7 +218,6 @@ public abstract class QRecordEntity
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -137,8 +227,7 @@ public abstract class QRecordEntity
|
||||
{
|
||||
QRecord qRecord = new QRecord();
|
||||
|
||||
List<QRecordEntityField> fieldList = getFieldList(this.getClass());
|
||||
for(QRecordEntityField qRecordEntityField : fieldList)
|
||||
for(QRecordEntityField qRecordEntityField : getFieldList(this.getClass()))
|
||||
{
|
||||
Serializable thisValue = (Serializable) qRecordEntityField.getGetter().invoke(this);
|
||||
Serializable originalValue = null;
|
||||
@ -153,6 +242,25 @@ public abstract class QRecordEntity
|
||||
}
|
||||
}
|
||||
|
||||
for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
|
||||
{
|
||||
List<? extends QRecordEntity> associatedEntities = (List<? extends QRecordEntity>) qRecordEntityAssociation.getGetter().invoke(this);
|
||||
String associationName = qRecordEntityAssociation.getAssociationAnnotation().name();
|
||||
|
||||
if(associatedEntities != null)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// do this so an empty list in the entity becomes an empty list in the QRecord //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
qRecord.withAssociatedRecords(associationName, new ArrayList<>());
|
||||
}
|
||||
|
||||
for(QRecordEntity associatedEntity : CollectionUtils.nonNullList(associatedEntities))
|
||||
{
|
||||
qRecord.withAssociatedRecord(associationName, associatedEntity.toQRecord());
|
||||
}
|
||||
}
|
||||
|
||||
return (qRecord);
|
||||
}
|
||||
catch(Exception e)
|
||||
@ -181,7 +289,15 @@ public abstract class QRecordEntity
|
||||
{
|
||||
String fieldName = getFieldNameFromGetter(possibleGetter);
|
||||
Optional<QField> fieldAnnotation = getQFieldAnnotation(c, fieldName);
|
||||
fieldList.add(new QRecordEntityField(fieldName, possibleGetter, setter.get(), possibleGetter.getReturnType(), fieldAnnotation.orElse(null)));
|
||||
|
||||
if(fieldAnnotation.isPresent())
|
||||
{
|
||||
fieldList.add(new QRecordEntityField(fieldName, possibleGetter, setter.get(), possibleGetter.getReturnType(), fieldAnnotation.orElse(null)));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Skipping field without @QField annotation", logPair("class", c.getSimpleName()), logPair("fieldName", fieldName));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -196,15 +312,73 @@ public abstract class QRecordEntity
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static List<QRecordEntityAssociation> getAssociationList(Class<? extends QRecordEntity> c)
|
||||
{
|
||||
if(!associationMapping.containsKey(c))
|
||||
{
|
||||
List<QRecordEntityAssociation> associationList = new ArrayList<>();
|
||||
for(Method possibleGetter : c.getMethods())
|
||||
{
|
||||
if(isGetter(possibleGetter))
|
||||
{
|
||||
Optional<Method> setter = getSetterForGetter(c, possibleGetter);
|
||||
|
||||
if(setter.isPresent())
|
||||
{
|
||||
String fieldName = getFieldNameFromGetter(possibleGetter);
|
||||
Optional<QAssociation> associationAnnotation = getQAssociationAnnotation(c, fieldName);
|
||||
|
||||
if(associationAnnotation.isPresent())
|
||||
{
|
||||
Class<? extends QRecordEntity> listTypeParam = (Class<? extends QRecordEntity>) getListTypeParam(possibleGetter.getReturnType(), possibleGetter.getAnnotatedReturnType());
|
||||
associationList.add(new QRecordEntityAssociation(fieldName, possibleGetter, setter.get(), listTypeParam, associationAnnotation.orElse(null)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.info("Getter method [" + possibleGetter.getName() + "] does not have a corresponding setter.");
|
||||
}
|
||||
}
|
||||
}
|
||||
associationMapping.put(c, associationList);
|
||||
}
|
||||
return (associationMapping.get(c));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Optional<QField> getQFieldAnnotation(Class<? extends QRecordEntity> c, String fieldName)
|
||||
{
|
||||
return (getAnnotationOnField(c, QField.class, fieldName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Optional<QAssociation> getQAssociationAnnotation(Class<? extends QRecordEntity> c, String fieldName)
|
||||
{
|
||||
return (getAnnotationOnField(c, QAssociation.class, fieldName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static <A extends Annotation> Optional<A> getAnnotationOnField(Class<? extends QRecordEntity> c, Class<A> annotationClass, String fieldName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Field field = c.getDeclaredField(fieldName);
|
||||
return (Optional.ofNullable(field.getAnnotation(QField.class)));
|
||||
return (Optional.ofNullable(field.getAnnotation(annotationClass)));
|
||||
}
|
||||
catch(NoSuchFieldException e)
|
||||
{
|
||||
@ -239,7 +413,7 @@ public abstract class QRecordEntity
|
||||
{
|
||||
if(method.getParameterTypes().length == 0 && method.getName().matches("^get[A-Z].*"))
|
||||
{
|
||||
if(isSupportedFieldType(method.getReturnType()))
|
||||
if(isSupportedFieldType(method.getReturnType()) || isSupportedAssociation(method.getReturnType(), method.getAnnotatedReturnType()))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
@ -304,4 +478,41 @@ public abstract class QRecordEntity
|
||||
/////////////////////////////////////////////
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static boolean isSupportedAssociation(Class<?> returnType, AnnotatedType annotatedType)
|
||||
{
|
||||
Class<?> listTypeParam = getListTypeParam(returnType, annotatedType);
|
||||
return (listTypeParam != null && QRecordEntity.class.isAssignableFrom(listTypeParam));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Class<?> getListTypeParam(Class<?> listType, AnnotatedType annotatedType)
|
||||
{
|
||||
if(listType.equals(List.class))
|
||||
{
|
||||
if(annotatedType instanceof AnnotatedParameterizedType apt)
|
||||
{
|
||||
AnnotatedType[] annotatedActualTypeArguments = apt.getAnnotatedActualTypeArguments();
|
||||
for(AnnotatedType annotatedActualTypeArgument : annotatedActualTypeArguments)
|
||||
{
|
||||
Type type = annotatedActualTypeArgument.getType();
|
||||
if(type instanceof Class<?> c)
|
||||
{
|
||||
return (c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.lang.reflect.Method;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Reflective information about an association in a QRecordEntity
|
||||
*******************************************************************************/
|
||||
public class QRecordEntityAssociation
|
||||
{
|
||||
private final String fieldName;
|
||||
private final Method getter;
|
||||
private final Method setter;
|
||||
|
||||
private final Class<? extends QRecordEntity> associatedType;
|
||||
|
||||
private final QAssociation associationAnnotation;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor.
|
||||
*******************************************************************************/
|
||||
public QRecordEntityAssociation(String fieldName, Method getter, Method setter, Class<? extends QRecordEntity> associatedType, QAssociation associationAnnotation)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
this.associatedType = associatedType;
|
||||
this.associationAnnotation = associationAnnotation;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fieldName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getFieldName()
|
||||
{
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for getter
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Method getGetter()
|
||||
{
|
||||
return getter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for setter
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Method getSetter()
|
||||
{
|
||||
return setter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for associatedType
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Class<? extends QRecordEntity> getAssociatedType()
|
||||
{
|
||||
return associatedType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for associationAnnotation
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QAssociation getAssociationAnnotation()
|
||||
{
|
||||
return associationAnnotation;
|
||||
}
|
||||
|
||||
}
|
@ -32,6 +32,9 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
*******************************************************************************/
|
||||
public abstract class MetaDataProducer<T extends TopLevelMetaDataInterface>
|
||||
{
|
||||
public static final int DEFAULT_SORT_ORDER = 500;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Produce the metaData object. Generally, you don't want to add it to the instance
|
||||
@ -43,11 +46,13 @@ public abstract class MetaDataProducer<T extends TopLevelMetaDataInterface>
|
||||
|
||||
/*******************************************************************************
|
||||
** In case this producer needs to run before (or after) others, this method
|
||||
** can help influence that (e.g., if used by MetaDataProducerHelper).
|
||||
** can control influence that (e.g., if used by MetaDataProducerHelper).
|
||||
**
|
||||
** Smaller values run first.
|
||||
*******************************************************************************/
|
||||
public int getSortOrder()
|
||||
{
|
||||
return (500);
|
||||
return (DEFAULT_SORT_ORDER);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,6 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
@ -38,17 +37,24 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
**
|
||||
*******************************************************************************/
|
||||
@JsonDeserialize(using = QBackendMetaDataDeserializer.class)
|
||||
public class QBackendMetaData
|
||||
public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
{
|
||||
private String name;
|
||||
private String backendType;
|
||||
|
||||
private Boolean usesVariants = false;
|
||||
private String variantOptionsTableName;
|
||||
|
||||
private Set<Capability> enabledCapabilities = new HashSet<>();
|
||||
private Set<Capability> disabledCapabilities = new HashSet<>();
|
||||
|
||||
private Boolean usesVariants = false;
|
||||
private String variantOptionsTableIdField;
|
||||
private String variantOptionsTableNameField;
|
||||
private String variantOptionsTableTypeField;
|
||||
private String variantOptionsTableTypeValue;
|
||||
private String variantOptionsTableUsernameField;
|
||||
private String variantOptionsTablePasswordField;
|
||||
private String variantOptionsTableApiKeyField;
|
||||
private String variantOptionsTableName;
|
||||
|
||||
// todo - at some point, we may want to apply this to secret properties on subclasses?
|
||||
// @JsonFilter("secretsFilter")
|
||||
|
||||
@ -59,6 +65,10 @@ public class QBackendMetaData
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData()
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// by default, we will turn off the query stats capability on all backends //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
withoutCapability(Capability.QUERY_STATS);
|
||||
}
|
||||
|
||||
|
||||
@ -199,6 +209,10 @@ public class QBackendMetaData
|
||||
public void setEnabledCapabilities(Set<Capability> enabledCapabilities)
|
||||
{
|
||||
this.enabledCapabilities = enabledCapabilities;
|
||||
if(this.disabledCapabilities != null)
|
||||
{
|
||||
this.disabledCapabilities.removeAll(enabledCapabilities);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -209,7 +223,7 @@ public class QBackendMetaData
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withEnabledCapabilities(Set<Capability> enabledCapabilities)
|
||||
{
|
||||
this.enabledCapabilities = enabledCapabilities;
|
||||
setEnabledCapabilities(enabledCapabilities);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -221,7 +235,10 @@ public class QBackendMetaData
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withCapabilities(Set<Capability> enabledCapabilities)
|
||||
{
|
||||
this.enabledCapabilities = enabledCapabilities;
|
||||
for(Capability enabledCapability : enabledCapabilities)
|
||||
{
|
||||
withCapability(enabledCapability);
|
||||
}
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -238,6 +255,7 @@ public class QBackendMetaData
|
||||
this.enabledCapabilities = new HashSet<>();
|
||||
}
|
||||
this.enabledCapabilities.add(capability);
|
||||
this.disabledCapabilities.remove(capability);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -249,11 +267,10 @@ public class QBackendMetaData
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withCapabilities(Capability... enabledCapabilities)
|
||||
{
|
||||
if(this.enabledCapabilities == null)
|
||||
for(Capability enabledCapability : enabledCapabilities)
|
||||
{
|
||||
this.enabledCapabilities = new HashSet<>();
|
||||
withCapability(enabledCapability);
|
||||
}
|
||||
this.enabledCapabilities.addAll(Arrays.stream(enabledCapabilities).toList());
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -277,6 +294,10 @@ public class QBackendMetaData
|
||||
public void setDisabledCapabilities(Set<Capability> disabledCapabilities)
|
||||
{
|
||||
this.disabledCapabilities = disabledCapabilities;
|
||||
if(this.enabledCapabilities != null)
|
||||
{
|
||||
this.enabledCapabilities.removeAll(disabledCapabilities);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -287,7 +308,7 @@ public class QBackendMetaData
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withDisabledCapabilities(Set<Capability> disabledCapabilities)
|
||||
{
|
||||
this.disabledCapabilities = disabledCapabilities;
|
||||
setDisabledCapabilities(disabledCapabilities);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -299,11 +320,10 @@ public class QBackendMetaData
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withoutCapabilities(Capability... disabledCapabilities)
|
||||
{
|
||||
if(this.disabledCapabilities == null)
|
||||
for(Capability disabledCapability : disabledCapabilities)
|
||||
{
|
||||
this.disabledCapabilities = new HashSet<>();
|
||||
withoutCapability(disabledCapability);
|
||||
}
|
||||
this.disabledCapabilities.addAll(Arrays.stream(disabledCapabilities).toList());
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -315,7 +335,10 @@ public class QBackendMetaData
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withoutCapabilities(Set<Capability> disabledCapabilities)
|
||||
{
|
||||
this.disabledCapabilities = disabledCapabilities;
|
||||
for(Capability disabledCapability : disabledCapabilities)
|
||||
{
|
||||
withCapability(disabledCapability);
|
||||
}
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -332,6 +355,7 @@ public class QBackendMetaData
|
||||
this.disabledCapabilities = new HashSet<>();
|
||||
}
|
||||
this.disabledCapabilities.add(capability);
|
||||
this.enabledCapabilities.remove(capability);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -381,7 +405,224 @@ public class QBackendMetaData
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantsOptionTableName
|
||||
** Getter for variantOptionsTableIdField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableIdField()
|
||||
{
|
||||
return (this.variantOptionsTableIdField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableIdField
|
||||
*******************************************************************************/
|
||||
public void setVariantOptionsTableIdField(String variantOptionsTableIdField)
|
||||
{
|
||||
this.variantOptionsTableIdField = variantOptionsTableIdField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableIdField
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withVariantOptionsTableIdField(String variantOptionsTableIdField)
|
||||
{
|
||||
this.variantOptionsTableIdField = variantOptionsTableIdField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableNameField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableNameField()
|
||||
{
|
||||
return (this.variantOptionsTableNameField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableNameField
|
||||
*******************************************************************************/
|
||||
public void setVariantOptionsTableNameField(String variantOptionsTableNameField)
|
||||
{
|
||||
this.variantOptionsTableNameField = variantOptionsTableNameField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableNameField
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withVariantOptionsTableNameField(String variantOptionsTableNameField)
|
||||
{
|
||||
this.variantOptionsTableNameField = variantOptionsTableNameField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableTypeField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableTypeField()
|
||||
{
|
||||
return (this.variantOptionsTableTypeField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableTypeField
|
||||
*******************************************************************************/
|
||||
public void setVariantOptionsTableTypeField(String variantOptionsTableTypeField)
|
||||
{
|
||||
this.variantOptionsTableTypeField = variantOptionsTableTypeField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableTypeField
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withVariantOptionsTableTypeField(String variantOptionsTableTypeField)
|
||||
{
|
||||
this.variantOptionsTableTypeField = variantOptionsTableTypeField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableTypeValue
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableTypeValue()
|
||||
{
|
||||
return (this.variantOptionsTableTypeValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableTypeValue
|
||||
*******************************************************************************/
|
||||
public void setVariantOptionsTableTypeValue(String variantOptionsTableTypeValue)
|
||||
{
|
||||
this.variantOptionsTableTypeValue = variantOptionsTableTypeValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableTypeValue
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withVariantOptionsTableTypeValue(String variantOptionsTableTypeValue)
|
||||
{
|
||||
this.variantOptionsTableTypeValue = variantOptionsTableTypeValue;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableUsernameField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableUsernameField()
|
||||
{
|
||||
return (this.variantOptionsTableUsernameField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableUsernameField
|
||||
*******************************************************************************/
|
||||
public void setVariantOptionsTableUsernameField(String variantOptionsTableUsernameField)
|
||||
{
|
||||
this.variantOptionsTableUsernameField = variantOptionsTableUsernameField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableUsernameField
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withVariantOptionsTableUsernameField(String variantOptionsTableUsernameField)
|
||||
{
|
||||
this.variantOptionsTableUsernameField = variantOptionsTableUsernameField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTablePasswordField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTablePasswordField()
|
||||
{
|
||||
return (this.variantOptionsTablePasswordField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTablePasswordField
|
||||
*******************************************************************************/
|
||||
public void setVariantOptionsTablePasswordField(String variantOptionsTablePasswordField)
|
||||
{
|
||||
this.variantOptionsTablePasswordField = variantOptionsTablePasswordField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTablePasswordField
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withVariantOptionsTablePasswordField(String variantOptionsTablePasswordField)
|
||||
{
|
||||
this.variantOptionsTablePasswordField = variantOptionsTablePasswordField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableApiKeyField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableApiKeyField()
|
||||
{
|
||||
return (this.variantOptionsTableApiKeyField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableApiKeyField
|
||||
*******************************************************************************/
|
||||
public void setVariantOptionsTableApiKeyField(String variantOptionsTableApiKeyField)
|
||||
{
|
||||
this.variantOptionsTableApiKeyField = variantOptionsTableApiKeyField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableApiKeyField
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withVariantOptionsTableApiKeyField(String variantOptionsTableApiKeyField)
|
||||
{
|
||||
this.variantOptionsTableApiKeyField = variantOptionsTableApiKeyField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableName
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableName()
|
||||
{
|
||||
@ -391,7 +632,7 @@ public class QBackendMetaData
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantsOptionTableName
|
||||
** Setter for variantOptionsTableName
|
||||
*******************************************************************************/
|
||||
public void setVariantOptionsTableName(String variantOptionsTableName)
|
||||
{
|
||||
@ -401,12 +642,22 @@ public class QBackendMetaData
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantsOptionTableName
|
||||
** Fluent setter for variantOptionsTableName
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withVariantsOptionTableName(String variantsOptionTableName)
|
||||
public QBackendMetaData withVariantOptionsTableName(String variantOptionsTableName)
|
||||
{
|
||||
this.variantOptionsTableName = variantsOptionTableName;
|
||||
this.variantOptionsTableName = variantOptionsTableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void addSelfToInstance(QInstance qInstance)
|
||||
{
|
||||
qInstance.addBackend(this);
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ public class QInstance
|
||||
private Map<String, QQueueProviderMetaData> queueProviders = new LinkedHashMap<>();
|
||||
private Map<String, QQueueMetaData> queues = new LinkedHashMap<>();
|
||||
|
||||
private Map<String, QMiddlewareInstanceMetaData> middlewareMetaData = new LinkedHashMap<>();
|
||||
private Map<String, QSupplementalInstanceMetaData> supplementalMetaData = new LinkedHashMap<>();
|
||||
|
||||
private Map<String, String> environmentValues = new LinkedHashMap<>();
|
||||
private String defaultTimeZoneId = "UTC";
|
||||
@ -1083,60 +1083,60 @@ public class QInstance
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for middlewareMetaData
|
||||
** Getter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public Map<String, QMiddlewareInstanceMetaData> getMiddlewareMetaData()
|
||||
public Map<String, QSupplementalInstanceMetaData> getSupplementalMetaData()
|
||||
{
|
||||
return (this.middlewareMetaData);
|
||||
return (this.supplementalMetaData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for middlewareMetaData
|
||||
** Getter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QMiddlewareInstanceMetaData getMiddlewareMetaData(String type)
|
||||
public QSupplementalInstanceMetaData getSupplementalMetaData(String type)
|
||||
{
|
||||
if(this.middlewareMetaData == null)
|
||||
if(this.supplementalMetaData == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
return this.middlewareMetaData.get(type);
|
||||
return this.supplementalMetaData.get(type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for middlewareMetaData
|
||||
** Setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public void setMiddlewareMetaData(Map<String, QMiddlewareInstanceMetaData> middlewareMetaData)
|
||||
public void setSupplementalMetaData(Map<String, QSupplementalInstanceMetaData> supplementalMetaData)
|
||||
{
|
||||
this.middlewareMetaData = middlewareMetaData;
|
||||
this.supplementalMetaData = supplementalMetaData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for middlewareMetaData
|
||||
** Fluent setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QInstance withMiddlewareMetaData(Map<String, QMiddlewareInstanceMetaData> middlewareMetaData)
|
||||
public QInstance withSupplementalMetaData(Map<String, QSupplementalInstanceMetaData> supplementalMetaData)
|
||||
{
|
||||
this.middlewareMetaData = middlewareMetaData;
|
||||
this.supplementalMetaData = supplementalMetaData;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for middlewareMetaData
|
||||
** Fluent setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QInstance withMiddlewareMetaData(QMiddlewareInstanceMetaData middlewareMetaData)
|
||||
public QInstance withSupplementalMetaData(QSupplementalInstanceMetaData supplementalMetaData)
|
||||
{
|
||||
if(this.middlewareMetaData == null)
|
||||
if(this.supplementalMetaData == null)
|
||||
{
|
||||
this.middlewareMetaData = new HashMap<>();
|
||||
this.supplementalMetaData = new HashMap<>();
|
||||
}
|
||||
this.middlewareMetaData.put(middlewareMetaData.getType(), middlewareMetaData);
|
||||
this.supplementalMetaData.put(supplementalMetaData.getType(), supplementalMetaData);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
@ -27,42 +27,16 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base-class for instance-level meta-data defined for a specific middleware.
|
||||
** Base-class for instance-level meta-data defined by some supplemental module, etc,
|
||||
** outside of qqq core
|
||||
*******************************************************************************/
|
||||
public abstract class QMiddlewareInstanceMetaData
|
||||
public abstract class QSupplementalInstanceMetaData
|
||||
{
|
||||
protected String type;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
*******************************************************************************/
|
||||
public String getType()
|
||||
{
|
||||
return (this.type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for type
|
||||
*******************************************************************************/
|
||||
public void setType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for type
|
||||
*******************************************************************************/
|
||||
public QMiddlewareInstanceMetaData withType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
return (this);
|
||||
}
|
||||
public abstract String getType();
|
||||
|
||||
|
||||
|
@ -28,8 +28,8 @@ import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.templates.RenderTemplateAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.templates.RenderTemplateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.templates.RenderTemplateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.templates.RenderTemplateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.templates.RenderTemplateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.templates.TemplateType;
|
||||
|
||||
|
||||
|
@ -85,7 +85,7 @@ public class QFieldMetaData implements Cloneable
|
||||
|
||||
private List<FieldAdornment> adornments;
|
||||
|
||||
private Map<String, QMiddlewareFieldMetaData> middlewareMetaData;
|
||||
private Map<String, QSupplementalFieldMetaData> supplementalMetaData;
|
||||
|
||||
|
||||
|
||||
@ -840,60 +840,60 @@ public class QFieldMetaData implements Cloneable
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for middlewareMetaData
|
||||
** Getter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public Map<String, QMiddlewareFieldMetaData> getMiddlewareMetaData()
|
||||
public Map<String, QSupplementalFieldMetaData> getSupplementalMetaData()
|
||||
{
|
||||
return (this.middlewareMetaData);
|
||||
return (this.supplementalMetaData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for middlewareMetaData
|
||||
** Getter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QMiddlewareFieldMetaData getMiddlewareMetaData(String type)
|
||||
public QSupplementalFieldMetaData getSupplementalMetaData(String type)
|
||||
{
|
||||
if(this.middlewareMetaData == null)
|
||||
if(this.supplementalMetaData == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
return this.middlewareMetaData.get(type);
|
||||
return this.supplementalMetaData.get(type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for middlewareMetaData
|
||||
** Setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public void setMiddlewareMetaData(Map<String, QMiddlewareFieldMetaData> middlewareMetaData)
|
||||
public void setSupplementalMetaData(Map<String, QSupplementalFieldMetaData> supplementalMetaData)
|
||||
{
|
||||
this.middlewareMetaData = middlewareMetaData;
|
||||
this.supplementalMetaData = supplementalMetaData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for middlewareMetaData
|
||||
** Fluent setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QFieldMetaData withMiddlewareMetaData(Map<String, QMiddlewareFieldMetaData> middlewareMetaData)
|
||||
public QFieldMetaData withSupplementalMetaData(Map<String, QSupplementalFieldMetaData> supplementalMetaData)
|
||||
{
|
||||
this.middlewareMetaData = middlewareMetaData;
|
||||
this.supplementalMetaData = supplementalMetaData;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for middlewareMetaData
|
||||
** Fluent setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QFieldMetaData withMiddlewareMetaData(QMiddlewareFieldMetaData middlewareMetaData)
|
||||
public QFieldMetaData withSupplementalMetaData(QSupplementalFieldMetaData supplementalMetaData)
|
||||
{
|
||||
if(this.middlewareMetaData == null)
|
||||
if(this.supplementalMetaData == null)
|
||||
{
|
||||
this.middlewareMetaData = new HashMap<>();
|
||||
this.supplementalMetaData = new HashMap<>();
|
||||
}
|
||||
this.middlewareMetaData.put(middlewareMetaData.getType(), middlewareMetaData);
|
||||
this.supplementalMetaData.put(supplementalMetaData.getType(), supplementalMetaData);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base-class for field-level meta-data defined by some supplemental module, etc,
|
||||
** outside of qqq core
|
||||
*******************************************************************************/
|
||||
public abstract class QSupplementalFieldMetaData
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
*******************************************************************************/
|
||||
public abstract String getType();
|
||||
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.frontend;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
@ -38,14 +39,15 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public class QFrontendFieldMetaData
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
private QFieldType type;
|
||||
private boolean isRequired;
|
||||
private boolean isEditable;
|
||||
private boolean isHeavy;
|
||||
private String possibleValueSourceName;
|
||||
private String displayFormat;
|
||||
private String name;
|
||||
private String label;
|
||||
private QFieldType type;
|
||||
private boolean isRequired;
|
||||
private boolean isEditable;
|
||||
private boolean isHeavy;
|
||||
private String possibleValueSourceName;
|
||||
private String displayFormat;
|
||||
private Serializable defaultValue;
|
||||
|
||||
private List<FieldAdornment> adornments;
|
||||
|
||||
@ -69,6 +71,7 @@ public class QFrontendFieldMetaData
|
||||
this.possibleValueSourceName = fieldMetaData.getPossibleValueSourceName();
|
||||
this.displayFormat = fieldMetaData.getDisplayFormat();
|
||||
this.adornments = fieldMetaData.getAdornments();
|
||||
this.defaultValue = fieldMetaData.getDefaultValue();
|
||||
}
|
||||
|
||||
|
||||
@ -170,4 +173,14 @@ public class QFrontendFieldMetaData
|
||||
return possibleValueSourceName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for defaultValue
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Serializable getDefaultValue()
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
@ -41,6 +42,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QSupplementalTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
@ -57,21 +59,22 @@ public class QFrontendTableMetaData
|
||||
private String label;
|
||||
private boolean isHidden;
|
||||
private String primaryKeyField;
|
||||
private String iconName;
|
||||
|
||||
private String iconName;
|
||||
|
||||
private Map<String, QFrontendFieldMetaData> fields;
|
||||
private List<QFieldSection> sections;
|
||||
|
||||
private List<QFrontendExposedJoin> exposedJoins;
|
||||
|
||||
private Set<String> capabilities;
|
||||
private Map<String, QFrontendFieldMetaData> fields;
|
||||
private List<QFieldSection> sections;
|
||||
private List<QFrontendExposedJoin> exposedJoins;
|
||||
private Map<String, QSupplementalTableMetaData> supplementalTableMetaData;
|
||||
private Set<String> capabilities;
|
||||
|
||||
private boolean readPermission;
|
||||
private boolean insertPermission;
|
||||
private boolean editPermission;
|
||||
private boolean deletePermission;
|
||||
|
||||
private boolean usesVariants;
|
||||
private String variantTableLabel;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// do not add setters. take values from the source-object in the constructor!! //
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
@ -81,13 +84,13 @@ public class QFrontendTableMetaData
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFrontendTableMetaData(AbstractActionInput actionInput, QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFields, boolean includeJoins)
|
||||
public QFrontendTableMetaData(AbstractActionInput actionInput, QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFullMetaData, boolean includeJoins)
|
||||
{
|
||||
this.name = tableMetaData.getName();
|
||||
this.label = tableMetaData.getLabel();
|
||||
this.isHidden = tableMetaData.getIsHidden();
|
||||
|
||||
if(includeFields)
|
||||
if(includeFullMetaData)
|
||||
{
|
||||
this.primaryKeyField = tableMetaData.getPrimaryKeyField();
|
||||
this.fields = new HashMap<>();
|
||||
@ -116,7 +119,7 @@ public class QFrontendTableMetaData
|
||||
QTableMetaData joinTable = qInstance.getTable(exposedJoin.getJoinTable());
|
||||
frontendExposedJoin.setLabel(exposedJoin.getLabel());
|
||||
frontendExposedJoin.setIsMany(exposedJoin.getIsMany());
|
||||
frontendExposedJoin.setJoinTable(new QFrontendTableMetaData(actionInput, backendForTable, joinTable, includeFields, false));
|
||||
frontendExposedJoin.setJoinTable(new QFrontendTableMetaData(actionInput, backendForTable, joinTable, includeFullMetaData, false));
|
||||
for(String joinName : exposedJoin.getJoinPath())
|
||||
{
|
||||
frontendExposedJoin.addJoin(qInstance.getJoin(joinName));
|
||||
@ -124,6 +127,28 @@ public class QFrontendTableMetaData
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// include supplemental meta data, based on if it's meant for full or partial frontend meta-data requests //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QSupplementalTableMetaData supplementalTableMetaData : CollectionUtils.nonNullMap(tableMetaData.getSupplementalMetaData()).values())
|
||||
{
|
||||
boolean include;
|
||||
if(includeFullMetaData)
|
||||
{
|
||||
include = supplementalTableMetaData.includeInFullFrontendMetaData();
|
||||
}
|
||||
else
|
||||
{
|
||||
include = supplementalTableMetaData.includeInPartialFrontendMetaData();
|
||||
}
|
||||
|
||||
if(include)
|
||||
{
|
||||
this.supplementalTableMetaData = Objects.requireNonNullElseGet(this.supplementalTableMetaData, HashMap::new);
|
||||
this.supplementalTableMetaData.put(supplementalTableMetaData.getType(), supplementalTableMetaData);
|
||||
}
|
||||
}
|
||||
|
||||
if(tableMetaData.getIcon() != null)
|
||||
{
|
||||
this.iconName = tableMetaData.getIcon().getName();
|
||||
@ -135,6 +160,13 @@ public class QFrontendTableMetaData
|
||||
insertPermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.INSERT);
|
||||
editPermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.EDIT);
|
||||
deletePermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.DELETE);
|
||||
|
||||
QBackendMetaData backend = actionInput.getInstance().getBackend(tableMetaData.getBackendName());
|
||||
if(backend != null && backend.getUsesVariants())
|
||||
{
|
||||
usesVariants = true;
|
||||
variantTableLabel = actionInput.getInstance().getTable(backend.getVariantOptionsTableName()).getLabel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -294,6 +326,17 @@ public class QFrontendTableMetaData
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for usesVariants
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getUsesVariants()
|
||||
{
|
||||
return usesVariants;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for exposedJoins
|
||||
**
|
||||
@ -302,4 +345,26 @@ public class QFrontendTableMetaData
|
||||
{
|
||||
return exposedJoins;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for supplementalTableMetaData
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QSupplementalTableMetaData> getSupplementalTableMetaData()
|
||||
{
|
||||
return supplementalTableMetaData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantTableLabel
|
||||
*******************************************************************************/
|
||||
public String getVariantTableLabel()
|
||||
{
|
||||
return (this.variantTableLabel);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.frontend;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Version of a variant for a frontend to see
|
||||
*******************************************************************************/
|
||||
public class QFrontendVariant
|
||||
{
|
||||
private Serializable id;
|
||||
private String name;
|
||||
private String type;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Serializable getId()
|
||||
{
|
||||
return (this.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
*******************************************************************************/
|
||||
public void setId(Serializable id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for id
|
||||
*******************************************************************************/
|
||||
public QFrontendVariant withId(Serializable id)
|
||||
{
|
||||
this.id = id;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
*******************************************************************************/
|
||||
public String getName()
|
||||
{
|
||||
return (this.name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for name
|
||||
*******************************************************************************/
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for name
|
||||
*******************************************************************************/
|
||||
public QFrontendVariant withName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
*******************************************************************************/
|
||||
public String getType()
|
||||
{
|
||||
return (this.type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for type
|
||||
*******************************************************************************/
|
||||
public void setType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for type
|
||||
*******************************************************************************/
|
||||
public QFrontendVariant withType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -24,6 +24,8 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
@ -36,7 +38,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
** MetaData definition of an App - an entity that organizes tables & processes
|
||||
** and can be arranged hierarchically (e.g, apps can contain other apps).
|
||||
*******************************************************************************/
|
||||
public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRules
|
||||
public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRules, TopLevelMetaDataInterface
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
@ -414,4 +416,14 @@ public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRu
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void addSelfToInstance(QInstance qInstance)
|
||||
{
|
||||
qInstance.addApp(this);
|
||||
}
|
||||
}
|
||||
|
@ -77,6 +77,21 @@ public class QPossibleValueSource implements TopLevelMetaDataInterface
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Create a new possible value source, for a table, with default settings.
|
||||
** e.g., name & table name from the tableName parameter; type=TABLE; and LABEL_ONLY format
|
||||
*******************************************************************************/
|
||||
public static QPossibleValueSource newForTable(String tableName)
|
||||
{
|
||||
return new QPossibleValueSource()
|
||||
.withName(tableName)
|
||||
.withType(QPossibleValueSourceType.TABLE)
|
||||
.withTableName(tableName)
|
||||
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -54,6 +54,9 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
private BasepullConfiguration basepullConfiguration;
|
||||
private QPermissionRules permissionRules;
|
||||
|
||||
private Integer minInputRecords = null;
|
||||
private Integer maxInputRecords = null;
|
||||
|
||||
private List<QStepMetaData> stepList; // these are the steps that are ran, by-default, in the order they are ran in
|
||||
private Map<String, QStepMetaData> steps; // this is the full map of possible steps
|
||||
|
||||
@ -61,6 +64,8 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
|
||||
private QScheduleMetaData schedule;
|
||||
|
||||
private Map<String, QSupplementalProcessMetaData> supplementalMetaData;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -544,4 +549,126 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
qInstance.addProcess(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public Map<String, QSupplementalProcessMetaData> getSupplementalMetaData()
|
||||
{
|
||||
return (this.supplementalMetaData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QSupplementalProcessMetaData getSupplementalMetaData(String type)
|
||||
{
|
||||
if(this.supplementalMetaData == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
return this.supplementalMetaData.get(type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public void setSupplementalMetaData(Map<String, QSupplementalProcessMetaData> supplementalMetaData)
|
||||
{
|
||||
this.supplementalMetaData = supplementalMetaData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QProcessMetaData withSupplementalMetaData(Map<String, QSupplementalProcessMetaData> supplementalMetaData)
|
||||
{
|
||||
this.supplementalMetaData = supplementalMetaData;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QProcessMetaData withSupplementalMetaData(QSupplementalProcessMetaData supplementalMetaData)
|
||||
{
|
||||
if(this.supplementalMetaData == null)
|
||||
{
|
||||
this.supplementalMetaData = new HashMap<>();
|
||||
}
|
||||
this.supplementalMetaData.put(supplementalMetaData.getType(), supplementalMetaData);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for minInputRecords
|
||||
*******************************************************************************/
|
||||
public Integer getMinInputRecords()
|
||||
{
|
||||
return (this.minInputRecords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for minInputRecords
|
||||
*******************************************************************************/
|
||||
public void setMinInputRecords(Integer minInputRecords)
|
||||
{
|
||||
this.minInputRecords = minInputRecords;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for minInputRecords
|
||||
*******************************************************************************/
|
||||
public QProcessMetaData withMinInputRecords(Integer minInputRecords)
|
||||
{
|
||||
this.minInputRecords = minInputRecords;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for maxInputRecords
|
||||
*******************************************************************************/
|
||||
public Integer getMaxInputRecords()
|
||||
{
|
||||
return (this.maxInputRecords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for maxInputRecords
|
||||
*******************************************************************************/
|
||||
public void setMaxInputRecords(Integer maxInputRecords)
|
||||
{
|
||||
this.maxInputRecords = maxInputRecords;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for maxInputRecords
|
||||
*******************************************************************************/
|
||||
public QProcessMetaData withMaxInputRecords(Integer maxInputRecords)
|
||||
{
|
||||
this.maxInputRecords = maxInputRecords;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.processes;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base-class for process-level meta-data defined by some supplemental module, etc,
|
||||
** outside of qqq core
|
||||
*******************************************************************************/
|
||||
public abstract class QSupplementalProcessMetaData
|
||||
{
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
*******************************************************************************/
|
||||
public abstract String getType();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void enrich(QInstanceEnricher qInstanceEnricher, QProcessMetaData process)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
////////////////////////
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void validate(QInstance qInstance, QProcessMetaData process, QInstanceValidator qInstanceValidator)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
////////////////////////
|
||||
}
|
||||
}
|
@ -22,6 +22,8 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.queues;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||
|
||||
|
||||
@ -34,7 +36,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaDa
|
||||
** The processName is the code that runs for messages found on the queue.
|
||||
** The schedule may not be used by all provider types, but defines when the queue is polled.
|
||||
*******************************************************************************/
|
||||
public class QQueueMetaData
|
||||
public class QQueueMetaData implements TopLevelMetaDataInterface
|
||||
{
|
||||
private String name;
|
||||
private String providerName;
|
||||
@ -213,4 +215,15 @@ public class QQueueMetaData
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void addSelfToInstance(QInstance qInstance)
|
||||
{
|
||||
qInstance.addQueue(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,9 +22,6 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.scheduleing;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta-data to define scheduled actions within QQQ.
|
||||
**
|
||||
@ -46,11 +43,8 @@ public class QScheduleMetaData
|
||||
private Integer initialDelaySeconds;
|
||||
private Integer initialDelayMillis;
|
||||
|
||||
private RunStrategy variantRunStrategy;
|
||||
private String backendVariant;
|
||||
private String variantTableName;
|
||||
private QQueryFilter variantFilter;
|
||||
private String variantFieldName;
|
||||
private RunStrategy variantRunStrategy;
|
||||
private String variantBackend;
|
||||
|
||||
|
||||
|
||||
@ -191,124 +185,31 @@ public class QScheduleMetaData
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for backendVariant
|
||||
** Getter for variantBackend
|
||||
*******************************************************************************/
|
||||
public String getBackendVariant()
|
||||
public String getVariantBackend()
|
||||
{
|
||||
return (this.backendVariant);
|
||||
return (this.variantBackend);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for backendVariant
|
||||
** Setter for variantBackend
|
||||
*******************************************************************************/
|
||||
public void setBackendVariant(String backendVariant)
|
||||
public void setVariantBackend(String variantBackend)
|
||||
{
|
||||
this.backendVariant = backendVariant;
|
||||
this.variantBackend = variantBackend;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for backendVariant
|
||||
** Fluent setter for variantBackend
|
||||
*******************************************************************************/
|
||||
public QScheduleMetaData withBackendVariant(String backendVariant)
|
||||
{
|
||||
this.backendVariant = backendVariant;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantTableName
|
||||
*******************************************************************************/
|
||||
public String getVariantTableName()
|
||||
{
|
||||
return (this.variantTableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantTableName
|
||||
*******************************************************************************/
|
||||
public void setVariantTableName(String variantTableName)
|
||||
{
|
||||
this.variantTableName = variantTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantTableName
|
||||
*******************************************************************************/
|
||||
public QScheduleMetaData withVariantTableName(String variantTableName)
|
||||
{
|
||||
this.variantTableName = variantTableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantFilter
|
||||
*******************************************************************************/
|
||||
public QQueryFilter getVariantFilter()
|
||||
{
|
||||
return (this.variantFilter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantFilter
|
||||
*******************************************************************************/
|
||||
public void setVariantFilter(QQueryFilter variantFilter)
|
||||
{
|
||||
this.variantFilter = variantFilter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantFilter
|
||||
*******************************************************************************/
|
||||
public QScheduleMetaData withVariantFilter(QQueryFilter variantFilter)
|
||||
{
|
||||
this.variantFilter = variantFilter;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantFieldName
|
||||
*******************************************************************************/
|
||||
public String getVariantFieldName()
|
||||
{
|
||||
return (this.variantFieldName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantFieldName
|
||||
*******************************************************************************/
|
||||
public void setVariantFieldName(String variantFieldName)
|
||||
{
|
||||
this.variantFieldName = variantFieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantFieldName
|
||||
*******************************************************************************/
|
||||
public QScheduleMetaData withVariantFieldName(String variantFieldName)
|
||||
{
|
||||
this.variantFieldName = variantFieldName;
|
||||
this.variantBackend = backendVariant;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
@ -33,9 +33,10 @@ public enum Capability
|
||||
TABLE_COUNT,
|
||||
TABLE_INSERT,
|
||||
TABLE_UPDATE,
|
||||
TABLE_DELETE
|
||||
TABLE_DELETE,
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// keep these values in sync with Capability.ts in qqq-frontend-core //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
QUERY_STATS
|
||||
}
|
||||
|
@ -23,42 +23,37 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base-class for table-level meta-data defined for a specific middleware.
|
||||
** Base-class for table-level meta-data defined by some supplemental module, etc,
|
||||
** outside of qqq core
|
||||
*******************************************************************************/
|
||||
public abstract class QMiddlewareTableMetaData
|
||||
public abstract class QSupplementalTableMetaData
|
||||
{
|
||||
protected String type;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean includeInPartialFrontendMetaData()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean includeInFullFrontendMetaData()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
*******************************************************************************/
|
||||
public String getType()
|
||||
{
|
||||
return (this.type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for type
|
||||
*******************************************************************************/
|
||||
public void setType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for type
|
||||
*******************************************************************************/
|
||||
public QMiddlewareTableMetaData withType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
return (this);
|
||||
}
|
||||
public abstract String getType();
|
||||
|
||||
|
||||
|
@ -99,7 +99,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
|
||||
private CacheOf cacheOf;
|
||||
|
||||
private Map<String, QMiddlewareTableMetaData> middlewareMetaData;
|
||||
private Map<String, QSupplementalTableMetaData> supplementalMetaData;
|
||||
|
||||
private List<ExposedJoin> exposedJoins;
|
||||
|
||||
@ -862,6 +862,10 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
public void setEnabledCapabilities(Set<Capability> enabledCapabilities)
|
||||
{
|
||||
this.enabledCapabilities = enabledCapabilities;
|
||||
if(this.disabledCapabilities != null)
|
||||
{
|
||||
this.disabledCapabilities.removeAll(enabledCapabilities);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -872,7 +876,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withEnabledCapabilities(Set<Capability> enabledCapabilities)
|
||||
{
|
||||
this.enabledCapabilities = enabledCapabilities;
|
||||
setEnabledCapabilities(enabledCapabilities);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -884,7 +888,10 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withCapabilities(Set<Capability> enabledCapabilities)
|
||||
{
|
||||
this.enabledCapabilities = enabledCapabilities;
|
||||
for(Capability enabledCapability : enabledCapabilities)
|
||||
{
|
||||
withCapability(enabledCapability);
|
||||
}
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -901,6 +908,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
this.enabledCapabilities = new HashSet<>();
|
||||
}
|
||||
this.enabledCapabilities.add(capability);
|
||||
this.disabledCapabilities.remove(capability);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -912,11 +920,10 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withCapabilities(Capability... enabledCapabilities)
|
||||
{
|
||||
if(this.enabledCapabilities == null)
|
||||
for(Capability enabledCapability : enabledCapabilities)
|
||||
{
|
||||
this.enabledCapabilities = new HashSet<>();
|
||||
withCapability(enabledCapability);
|
||||
}
|
||||
this.enabledCapabilities.addAll(Arrays.stream(enabledCapabilities).toList());
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -940,6 +947,10 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
public void setDisabledCapabilities(Set<Capability> disabledCapabilities)
|
||||
{
|
||||
this.disabledCapabilities = disabledCapabilities;
|
||||
if(this.enabledCapabilities != null)
|
||||
{
|
||||
this.enabledCapabilities.removeAll(disabledCapabilities);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -950,7 +961,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withDisabledCapabilities(Set<Capability> disabledCapabilities)
|
||||
{
|
||||
this.disabledCapabilities = disabledCapabilities;
|
||||
setDisabledCapabilities(disabledCapabilities);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -962,11 +973,10 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withoutCapabilities(Capability... disabledCapabilities)
|
||||
{
|
||||
if(this.disabledCapabilities == null)
|
||||
for(Capability disabledCapability : disabledCapabilities)
|
||||
{
|
||||
this.disabledCapabilities = new HashSet<>();
|
||||
withoutCapability(disabledCapability);
|
||||
}
|
||||
this.disabledCapabilities.addAll(Arrays.stream(disabledCapabilities).toList());
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -978,7 +988,10 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withoutCapabilities(Set<Capability> disabledCapabilities)
|
||||
{
|
||||
this.disabledCapabilities = disabledCapabilities;
|
||||
for(Capability disabledCapability : disabledCapabilities)
|
||||
{
|
||||
withCapability(disabledCapability);
|
||||
}
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -995,6 +1008,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
this.disabledCapabilities = new HashSet<>();
|
||||
}
|
||||
this.disabledCapabilities.add(capability);
|
||||
this.enabledCapabilities.remove(capability);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -1189,60 +1203,60 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for middlewareMetaData
|
||||
** Getter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public Map<String, QMiddlewareTableMetaData> getMiddlewareMetaData()
|
||||
public Map<String, QSupplementalTableMetaData> getSupplementalMetaData()
|
||||
{
|
||||
return (this.middlewareMetaData);
|
||||
return (this.supplementalMetaData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for middlewareMetaData
|
||||
** Getter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QMiddlewareTableMetaData getMiddlewareMetaData(String type)
|
||||
public QSupplementalTableMetaData getSupplementalMetaData(String type)
|
||||
{
|
||||
if(this.middlewareMetaData == null)
|
||||
if(this.supplementalMetaData == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
return this.middlewareMetaData.get(type);
|
||||
return this.supplementalMetaData.get(type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for middlewareMetaData
|
||||
** Setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public void setMiddlewareMetaData(Map<String, QMiddlewareTableMetaData> middlewareMetaData)
|
||||
public void setSupplementalMetaData(Map<String, QSupplementalTableMetaData> supplementalMetaData)
|
||||
{
|
||||
this.middlewareMetaData = middlewareMetaData;
|
||||
this.supplementalMetaData = supplementalMetaData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for middlewareMetaData
|
||||
** Fluent setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withMiddlewareMetaData(Map<String, QMiddlewareTableMetaData> middlewareMetaData)
|
||||
public QTableMetaData withSupplementalMetaData(Map<String, QSupplementalTableMetaData> supplementalMetaData)
|
||||
{
|
||||
this.middlewareMetaData = middlewareMetaData;
|
||||
this.supplementalMetaData = supplementalMetaData;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for middlewareMetaData
|
||||
** Fluent setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withMiddlewareMetaData(QMiddlewareTableMetaData middlewareMetaData)
|
||||
public QTableMetaData withSupplementalMetaData(QSupplementalTableMetaData supplementalMetaData)
|
||||
{
|
||||
if(this.middlewareMetaData == null)
|
||||
if(this.supplementalMetaData == null)
|
||||
{
|
||||
this.middlewareMetaData = new HashMap<>();
|
||||
this.supplementalMetaData = new HashMap<>();
|
||||
}
|
||||
this.middlewareMetaData.put(middlewareMetaData.getType(), middlewareMetaData);
|
||||
this.supplementalMetaData.put(supplementalMetaData.getType(), supplementalMetaData);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,7 @@ public class CacheUseCase
|
||||
//////////////////////////
|
||||
private UniqueKey cacheUniqueKey;
|
||||
private UniqueKey sourceUniqueKey;
|
||||
private boolean doCopySourcePrimaryKeyToCache = false;
|
||||
|
||||
private List<QQueryFilter> excludeRecordsMatching;
|
||||
|
||||
@ -222,4 +223,35 @@ public class CacheUseCase
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for doCopySourcePrimaryKeyToCache
|
||||
*******************************************************************************/
|
||||
public boolean getDoCopySourcePrimaryKeyToCache()
|
||||
{
|
||||
return (this.doCopySourcePrimaryKeyToCache);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for doCopySourcePrimaryKeyToCache
|
||||
*******************************************************************************/
|
||||
public void setDoCopySourcePrimaryKeyToCache(boolean doCopySourcePrimaryKeyToCache)
|
||||
{
|
||||
this.doCopySourcePrimaryKeyToCache = doCopySourcePrimaryKeyToCache;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for doCopySourcePrimaryKeyToCache
|
||||
*******************************************************************************/
|
||||
public CacheUseCase withDoCopySourcePrimaryKeyToCache(boolean doCopySourcePrimaryKeyToCache)
|
||||
{
|
||||
this.doCopySourcePrimaryKeyToCache = doCopySourcePrimaryKeyToCache;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,537 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.querystats;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QAssociation;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTable;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for QueryStat table
|
||||
*******************************************************************************/
|
||||
public class QueryStat extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "queryStat";
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Integer id;
|
||||
|
||||
@QField()
|
||||
private Instant startTimestamp;
|
||||
|
||||
@QField()
|
||||
private Instant firstResultTimestamp;
|
||||
|
||||
@QField()
|
||||
private Integer firstResultMillis;
|
||||
|
||||
@QField(possibleValueSourceName = QQQTable.TABLE_NAME, backendName = "qqq_table_id")
|
||||
private Integer tableId;
|
||||
|
||||
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS)
|
||||
private String action;
|
||||
|
||||
@QField(maxLength = 36, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS)
|
||||
private String sessionId;
|
||||
|
||||
@QField(maxLength = 64 * 1024 - 1, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS)
|
||||
private String queryText;
|
||||
|
||||
@QAssociation(name = "queryStatJoinTables")
|
||||
private List<QueryStatJoinTable> queryStatJoinTableList;
|
||||
|
||||
@QAssociation(name = "queryStatCriteriaFields")
|
||||
private List<QueryStatCriteriaField> queryStatCriteriaFieldList;
|
||||
|
||||
@QAssociation(name = "queryStatOrderByFields")
|
||||
private List<QueryStatOrderByField> queryStatOrderByFieldList;
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// non-persistent fields - used to help build the record //
|
||||
///////////////////////////////////////////////////////////
|
||||
private String tableName;
|
||||
private Set<String> joinTableNames;
|
||||
private QQueryFilter queryFilter;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default constructor
|
||||
*******************************************************************************/
|
||||
public QueryStat()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor that takes a QRecord
|
||||
*******************************************************************************/
|
||||
public QueryStat(QRecord record)
|
||||
{
|
||||
populateFromQRecord(record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return (this.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for id
|
||||
*******************************************************************************/
|
||||
public QueryStat withId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for startTimestamp
|
||||
*******************************************************************************/
|
||||
public Instant getStartTimestamp()
|
||||
{
|
||||
return (this.startTimestamp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for startTimestamp
|
||||
*******************************************************************************/
|
||||
public void setStartTimestamp(Instant startTimestamp)
|
||||
{
|
||||
this.startTimestamp = startTimestamp;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for startTimestamp
|
||||
*******************************************************************************/
|
||||
public QueryStat withStartTimestamp(Instant startTimestamp)
|
||||
{
|
||||
this.startTimestamp = startTimestamp;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for firstResultTimestamp
|
||||
*******************************************************************************/
|
||||
public Instant getFirstResultTimestamp()
|
||||
{
|
||||
return (this.firstResultTimestamp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for firstResultTimestamp
|
||||
*******************************************************************************/
|
||||
public void setFirstResultTimestamp(Instant firstResultTimestamp)
|
||||
{
|
||||
this.firstResultTimestamp = firstResultTimestamp;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for firstResultTimestamp
|
||||
*******************************************************************************/
|
||||
public QueryStat withFirstResultTimestamp(Instant firstResultTimestamp)
|
||||
{
|
||||
this.firstResultTimestamp = firstResultTimestamp;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for firstResultMillis
|
||||
*******************************************************************************/
|
||||
public Integer getFirstResultMillis()
|
||||
{
|
||||
return (this.firstResultMillis);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for firstResultMillis
|
||||
*******************************************************************************/
|
||||
public void setFirstResultMillis(Integer firstResultMillis)
|
||||
{
|
||||
this.firstResultMillis = firstResultMillis;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for firstResultMillis
|
||||
*******************************************************************************/
|
||||
public QueryStat withFirstResultMillis(Integer firstResultMillis)
|
||||
{
|
||||
this.firstResultMillis = firstResultMillis;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryText
|
||||
*******************************************************************************/
|
||||
public String getQueryText()
|
||||
{
|
||||
return (this.queryText);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queryText
|
||||
*******************************************************************************/
|
||||
public void setQueryText(String queryText)
|
||||
{
|
||||
this.queryText = queryText;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryText
|
||||
*******************************************************************************/
|
||||
public QueryStat withQueryText(String queryText)
|
||||
{
|
||||
this.queryText = queryText;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryStatJoinTableList
|
||||
*******************************************************************************/
|
||||
public List<QueryStatJoinTable> getQueryStatJoinTableList()
|
||||
{
|
||||
return (this.queryStatJoinTableList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queryStatJoinTableList
|
||||
*******************************************************************************/
|
||||
public void setQueryStatJoinTableList(List<QueryStatJoinTable> queryStatJoinTableList)
|
||||
{
|
||||
this.queryStatJoinTableList = queryStatJoinTableList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryStatJoinTableList
|
||||
*******************************************************************************/
|
||||
public QueryStat withQueryStatJoinTableList(List<QueryStatJoinTable> queryStatJoinTableList)
|
||||
{
|
||||
this.queryStatJoinTableList = queryStatJoinTableList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryStatCriteriaFieldList
|
||||
*******************************************************************************/
|
||||
public List<QueryStatCriteriaField> getQueryStatCriteriaFieldList()
|
||||
{
|
||||
return (this.queryStatCriteriaFieldList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queryStatCriteriaFieldList
|
||||
*******************************************************************************/
|
||||
public void setQueryStatCriteriaFieldList(List<QueryStatCriteriaField> queryStatCriteriaFieldList)
|
||||
{
|
||||
this.queryStatCriteriaFieldList = queryStatCriteriaFieldList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryStatCriteriaFieldList
|
||||
*******************************************************************************/
|
||||
public QueryStat withQueryStatCriteriaFieldList(List<QueryStatCriteriaField> queryStatCriteriaFieldList)
|
||||
{
|
||||
this.queryStatCriteriaFieldList = queryStatCriteriaFieldList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryStatOrderByFieldList
|
||||
*******************************************************************************/
|
||||
public List<QueryStatOrderByField> getQueryStatOrderByFieldList()
|
||||
{
|
||||
return (this.queryStatOrderByFieldList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queryStatOrderByFieldList
|
||||
*******************************************************************************/
|
||||
public void setQueryStatOrderByFieldList(List<QueryStatOrderByField> queryStatOrderByFieldList)
|
||||
{
|
||||
this.queryStatOrderByFieldList = queryStatOrderByFieldList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryStatOrderByFieldList
|
||||
*******************************************************************************/
|
||||
public QueryStat withQueryStatOrderByFieldList(List<QueryStatOrderByField> queryStatOrderByFieldList)
|
||||
{
|
||||
this.queryStatOrderByFieldList = queryStatOrderByFieldList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return (this.tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
*******************************************************************************/
|
||||
public void setTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableName
|
||||
*******************************************************************************/
|
||||
public QueryStat withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryFilter
|
||||
*******************************************************************************/
|
||||
public QQueryFilter getQueryFilter()
|
||||
{
|
||||
return (this.queryFilter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queryFilter
|
||||
*******************************************************************************/
|
||||
public void setQueryFilter(QQueryFilter queryFilter)
|
||||
{
|
||||
this.queryFilter = queryFilter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryFilter
|
||||
*******************************************************************************/
|
||||
public QueryStat withQueryFilter(QQueryFilter queryFilter)
|
||||
{
|
||||
this.queryFilter = queryFilter;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableId
|
||||
*******************************************************************************/
|
||||
public Integer getTableId()
|
||||
{
|
||||
return (this.tableId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableId
|
||||
*******************************************************************************/
|
||||
public void setTableId(Integer tableId)
|
||||
{
|
||||
this.tableId = tableId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableId
|
||||
*******************************************************************************/
|
||||
public QueryStat withTableId(Integer tableId)
|
||||
{
|
||||
this.tableId = tableId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for joinTableNames
|
||||
*******************************************************************************/
|
||||
public Set<String> getJoinTableNames()
|
||||
{
|
||||
return (this.joinTableNames);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for joinTableNames
|
||||
*******************************************************************************/
|
||||
public void setJoinTableNames(Set<String> joinTableNames)
|
||||
{
|
||||
this.joinTableNames = joinTableNames;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for joinTableNames
|
||||
*******************************************************************************/
|
||||
public QueryStat withJoinTableNames(Set<String> joinTableNames)
|
||||
{
|
||||
this.joinTableNames = joinTableNames;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for action
|
||||
*******************************************************************************/
|
||||
public String getAction()
|
||||
{
|
||||
return (this.action);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for action
|
||||
*******************************************************************************/
|
||||
public void setAction(String action)
|
||||
{
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for action
|
||||
*******************************************************************************/
|
||||
public QueryStat withAction(String action)
|
||||
{
|
||||
this.action = action;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sessionId
|
||||
*******************************************************************************/
|
||||
public String getSessionId()
|
||||
{
|
||||
return (this.sessionId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sessionId
|
||||
*******************************************************************************/
|
||||
public void setSessionId(String sessionId)
|
||||
{
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sessionId
|
||||
*******************************************************************************/
|
||||
public QueryStat withSessionId(String sessionId)
|
||||
{
|
||||
this.sessionId = sessionId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user