mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-20 06:00:44 +00:00
Compare commits
2 Commits
feature/bu
...
snapshot-f
Author | SHA1 | Date | |
---|---|---|---|
f7f40ffc1d | |||
c0ffd2fe0e |
@ -42,6 +42,7 @@ 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.audits.AuditsMetaDataProvider;
|
||||
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;
|
||||
@ -177,8 +178,8 @@ 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()));
|
||||
Integer auditTableId = getIdForName(AuditsMetaDataProvider.TABLE_NAME_AUDIT_TABLE, auditSingleInput.getAuditTableName());
|
||||
Integer auditUserId = getIdForName(AuditsMetaDataProvider.TABLE_NAME_AUDIT_USER, Objects.requireNonNullElse(auditSingleInput.getAuditUserName(), getSessionUserName()));
|
||||
Instant timestamp = Objects.requireNonNullElse(auditSingleInput.getTimestamp(), Instant.now());
|
||||
|
||||
//////////////////
|
||||
@ -267,7 +268,7 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Integer getIdForName(String tableName, String nameValue) throws QException
|
||||
Integer getIdForName(String tableName, String nameValue) throws QException
|
||||
{
|
||||
Pair<String, String> key = new Pair<>(tableName, nameValue);
|
||||
if(!cachedFetches.containsKey(key))
|
||||
@ -287,7 +288,7 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
||||
insertInput.setTableName(tableName);
|
||||
QRecord record = new QRecord().withValue("name", nameValue);
|
||||
|
||||
if(tableName.equals("auditTable"))
|
||||
if(tableName.equals(AuditsMetaDataProvider.TABLE_NAME_AUDIT_TABLE))
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(nameValue);
|
||||
if(table != null)
|
||||
|
@ -33,6 +33,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
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;
|
||||
@ -46,7 +47,9 @@ import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.audits.AuditsMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
@ -89,7 +92,7 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
List<QRecord> recordList = CollectionUtils.nonNullList(input.getRecordList()).stream()
|
||||
.filter(r -> CollectionUtils.nullSafeIsEmpty(r.getErrors())).toList();
|
||||
|
||||
AuditLevel auditLevel = getAuditLevel(tableActionInput);
|
||||
AuditLevel auditLevel = getAuditLevel(table);
|
||||
if(auditLevel == null || auditLevel.equals(AuditLevel.NONE) || CollectionUtils.nullSafeIsEmpty(recordList))
|
||||
{
|
||||
/////////////////////////////////////////////
|
||||
@ -269,7 +272,54 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
}
|
||||
|
||||
// new AuditAction().executeAsync(auditInput); // todo async??? maybe get that from rules???
|
||||
new AuditAction().execute(auditInput);
|
||||
AuditAction auditAction = new AuditAction();
|
||||
auditAction.execute(auditInput);
|
||||
|
||||
if(DMLType.INSERT.equals(dmlType))
|
||||
{
|
||||
if(getIsAuditTreeRoot(table))
|
||||
{
|
||||
/* not needed? */
|
||||
/*
|
||||
Integer auditTableId = auditAction.getIdForName("auditTable", table.getName());
|
||||
List<QRecord> auditTrees = recordList.stream().map(r ->
|
||||
new QRecord()
|
||||
.withValue("rootAuditTableId", auditTableId)
|
||||
.withValue("rootRecordId", r.getValueInteger(table.getPrimaryKeyField()))
|
||||
).toList();
|
||||
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName("audit tree"); // todo - from entity
|
||||
insertInput.setRecords(auditTrees);
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
*/
|
||||
}
|
||||
|
||||
for(String auditTreeParentTableName : CollectionUtils.nonNullList(getAuditTreeParentTableNames(table)))
|
||||
{
|
||||
Integer rootAuditTableId = auditAction.getIdForName(AuditsMetaDataProvider.TABLE_NAME_AUDIT_TABLE, auditTreeParentTableName);
|
||||
Integer nodeAuditTableId = auditAction.getIdForName(AuditsMetaDataProvider.TABLE_NAME_AUDIT_TABLE, table.getName());
|
||||
|
||||
List<QRecord> auditTreeNodes = new ArrayList<>();
|
||||
for(QRecord record : recordList)
|
||||
{
|
||||
Serializable rootRecordId = record.getValue("orderId"); // todo - figure this out, from joins...
|
||||
|
||||
auditTreeNodes.add(new QRecord()
|
||||
.withValue("rootAuditTableId", rootAuditTableId)
|
||||
.withValue("rootRecordId", rootRecordId)
|
||||
.withValue("nodeAuditTableId", nodeAuditTableId)
|
||||
.withValue("nodeRecordId", record.getValue(table.getPrimaryKeyField()))
|
||||
);
|
||||
}
|
||||
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(AuditsMetaDataProvider.TABLE_NAME_AUDIT_TREE);
|
||||
insertInput.setRecords(auditTreeNodes);
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
}
|
||||
}
|
||||
|
||||
long end = System.currentTimeMillis();
|
||||
LOG.trace("Audit performance", logPair("auditLevel", String.valueOf(auditLevel)), logPair("recordCount", recordList.size()), logPair("millis", (end - start)));
|
||||
}
|
||||
@ -390,9 +440,8 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static AuditLevel getAuditLevel(AbstractTableActionInput tableActionInput)
|
||||
public static AuditLevel getAuditLevel(QTableMetaData table)
|
||||
{
|
||||
QTableMetaData table = tableActionInput.getTable();
|
||||
if(table.getAuditRules() == null)
|
||||
{
|
||||
return (AuditLevel.NONE);
|
||||
@ -403,6 +452,36 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static boolean getIsAuditTreeRoot(QTableMetaData table)
|
||||
{
|
||||
if(table.getAuditRules() == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (table.getAuditRules().getIsAuditTreeRoot());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static List<String> getAuditTreeParentTableNames(QTableMetaData table)
|
||||
{
|
||||
if(table.getAuditRules() == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
return (table.getAuditRules().getAuditTreeParentTableNames());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -29,6 +29,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
|
||||
@ -36,9 +37,13 @@ import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
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.tables.Association;
|
||||
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.processes.implementations.audits.GetRecordAuditsStep;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -50,6 +55,10 @@ public class AuditsMetaDataProvider
|
||||
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";
|
||||
public static final String TABLE_NAME_AUDIT_TREE = "auditTree";
|
||||
|
||||
public static final String AUDIT_TREE_JOIN_AUDIT_TABLE_FOR_ROOT = "auditTreeJoinAuditTableForRoot";
|
||||
public static final String AUDIT_TREE_JOIN_AUDIT_TABLE_FOR_NODE = "auditTreeJoinAuditTableForNode";
|
||||
|
||||
|
||||
|
||||
@ -61,6 +70,21 @@ public class AuditsMetaDataProvider
|
||||
defineStandardAuditTables(instance, backendName, backendDetailEnricher);
|
||||
defineStandardAuditPossibleValueSources(instance);
|
||||
defineStandardAuditJoins(instance);
|
||||
defineProcessGetRecordAudits(instance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void defineProcessGetRecordAudits(QInstance instance)
|
||||
{
|
||||
instance.addProcess(new QProcessMetaData()
|
||||
.withName("getRecordAudits")
|
||||
.withStepList(List.of(new QBackendStepMetaData()
|
||||
.withName("step")
|
||||
.withCode(new QCodeReference(GetRecordAuditsStep.class)))));
|
||||
}
|
||||
|
||||
|
||||
@ -91,6 +115,20 @@ public class AuditsMetaDataProvider
|
||||
.withType(JoinType.ONE_TO_MANY)
|
||||
.withJoinOn(new JoinOn("id", "auditId")));
|
||||
|
||||
instance.addJoin(new QJoinMetaData()
|
||||
.withLeftTable(TABLE_NAME_AUDIT_TREE)
|
||||
.withRightTable(TABLE_NAME_AUDIT_TABLE)
|
||||
.withName(AUDIT_TREE_JOIN_AUDIT_TABLE_FOR_ROOT)
|
||||
.withType(JoinType.MANY_TO_ONE)
|
||||
.withJoinOn(new JoinOn("rootAuditTableId", "id")));
|
||||
|
||||
instance.addJoin(new QJoinMetaData()
|
||||
.withLeftTable(TABLE_NAME_AUDIT_TREE)
|
||||
.withRightTable(TABLE_NAME_AUDIT_TABLE)
|
||||
.withName(AUDIT_TREE_JOIN_AUDIT_TABLE_FOR_NODE)
|
||||
.withType(JoinType.MANY_TO_ONE)
|
||||
.withJoinOn(new JoinOn("nodeAuditTableId", "id")));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -141,6 +179,7 @@ public class AuditsMetaDataProvider
|
||||
rs.add(enrich(backendDetailEnricher, defineAuditTableTable(backendName)));
|
||||
rs.add(enrich(backendDetailEnricher, defineAuditTable(backendName)));
|
||||
rs.add(enrich(backendDetailEnricher, defineAuditDetailTable(backendName)));
|
||||
rs.add(enrich(backendDetailEnricher, defineAuditTreeTable(backendName)));
|
||||
return (rs);
|
||||
}
|
||||
|
||||
@ -217,6 +256,10 @@ public class AuditsMetaDataProvider
|
||||
.withRecordLabelFormat("%s %s")
|
||||
.withRecordLabelFields("auditTableId", "recordId")
|
||||
.withPrimaryKeyField("id")
|
||||
.withAssociation(new Association()
|
||||
.withName("auditDetails")
|
||||
.withAssociatedTableName(TABLE_NAME_AUDIT_DETAIL)
|
||||
.withJoinName(QJoinMetaData.makeInferredJoinName(TABLE_NAME_AUDIT, TABLE_NAME_AUDIT_DETAIL)))
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("auditTableId", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_AUDIT_TABLE))
|
||||
.withField(new QFieldMetaData("auditUserId", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_AUDIT_USER))
|
||||
@ -249,4 +292,26 @@ public class AuditsMetaDataProvider
|
||||
.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QTableMetaData defineAuditTreeTable(String backendName)
|
||||
{
|
||||
return new QTableMetaData()
|
||||
.withName(TABLE_NAME_AUDIT_TREE)
|
||||
.withBackendName(backendName)
|
||||
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE))
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("id")
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("rootAuditTableId", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_AUDIT))
|
||||
.withField(new QFieldMetaData("rootRecordId", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("nodeAuditTableId", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_AUDIT))
|
||||
.withField(new QFieldMetaData("nodeRecordId", QFieldType.INTEGER))
|
||||
.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,9 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.audits;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -29,6 +32,9 @@ public class QAuditRules
|
||||
{
|
||||
private AuditLevel auditLevel;
|
||||
|
||||
private boolean isAuditTreeRoot = false;
|
||||
private List<String> auditTreeParentTableNames = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -71,4 +77,66 @@ public class QAuditRules
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for isAuditTreeRoot
|
||||
*******************************************************************************/
|
||||
public boolean getIsAuditTreeRoot()
|
||||
{
|
||||
return (this.isAuditTreeRoot);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isAuditTreeRoot
|
||||
*******************************************************************************/
|
||||
public void setIsAuditTreeRoot(boolean isAuditTreeRoot)
|
||||
{
|
||||
this.isAuditTreeRoot = isAuditTreeRoot;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for isAuditTreeRoot
|
||||
*******************************************************************************/
|
||||
public QAuditRules withIsAuditTreeRoot(boolean isAuditTreeRoot)
|
||||
{
|
||||
this.isAuditTreeRoot = isAuditTreeRoot;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for auditTreeParentTableNames
|
||||
*******************************************************************************/
|
||||
public List<String> getAuditTreeParentTableNames()
|
||||
{
|
||||
return (this.auditTreeParentTableNames);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for auditTreeParentTableNames
|
||||
*******************************************************************************/
|
||||
public void setAuditTreeParentTableNames(List<String> auditTreeParentTableNames)
|
||||
{
|
||||
this.auditTreeParentTableNames = auditTreeParentTableNames;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for auditTreeParentTableNames
|
||||
*******************************************************************************/
|
||||
public QAuditRules withAuditTreeParentTableNames(List<String> auditTreeParentTableNames)
|
||||
{
|
||||
this.auditTreeParentTableNames = auditTreeParentTableNames;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.processes.implementations.audits;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.TableMetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
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.metadata.TableMetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.audits.AuditsMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** This is a single-step process used to look up audits for a record.
|
||||
*******************************************************************************/
|
||||
public class GetRecordAuditsStep implements BackendStep
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(GetRecordAuditsStep.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
String tableName = runBackendStepInput.getValueString("tableName");
|
||||
Integer recordId = runBackendStepInput.getValueInteger("recordId");
|
||||
String sortDirection = runBackendStepInput.getValueString("sortDirection");
|
||||
|
||||
Integer limit = 1000;
|
||||
|
||||
/////////////////////////////////////////
|
||||
// make sure user may query this table //
|
||||
/////////////////////////////////////////
|
||||
PermissionsHelper.checkTablePermissionThrowing(new QueryInput().withTableName(tableName), TablePermissionSubType.READ);
|
||||
PermissionsHelper.checkTablePermissionThrowing(new QueryInput().withTableName(AuditsMetaDataProvider.TABLE_NAME_AUDIT), TablePermissionSubType.READ);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// set up filter for audits - always start with the record in question //
|
||||
// possibly add in other pairs of table/recordId based on the tree... //
|
||||
// combine all of those options in a filter of OR's //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
QQueryFilter auditRecordFilter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR);
|
||||
|
||||
auditRecordFilter.addSubFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria(AuditsMetaDataProvider.TABLE_NAME_AUDIT_TABLE + ".name", QCriteriaOperator.EQUALS, tableName))
|
||||
.withCriteria(new QFilterCriteria("recordId", QCriteriaOperator.EQUALS, recordId)));
|
||||
|
||||
Set<Integer> otherAuditTableIds = new HashSet<>();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the table is part of an audit tree, then find other table/recordId pairs to look for in the audits query //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
QAuditRules auditRules = Objects.requireNonNullElseGet(table.getAuditRules(), QAuditRules::new);
|
||||
if(auditRules.getIsAuditTreeRoot())
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(AuditsMetaDataProvider.TABLE_NAME_AUDIT_TREE);
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("rootTable.name", QCriteriaOperator.EQUALS, tableName)));
|
||||
|
||||
queryInput.withQueryJoin(new QueryJoin(AuditsMetaDataProvider.TABLE_NAME_AUDIT_TABLE)
|
||||
.withJoinMetaData(QContext.getQInstance().getJoin(AuditsMetaDataProvider.AUDIT_TREE_JOIN_AUDIT_TABLE_FOR_ROOT))
|
||||
.withAlias("rootTable"));
|
||||
|
||||
for(QRecord auditTreeRecord : new QueryAction().execute(queryInput).getRecords())
|
||||
{
|
||||
otherAuditTableIds.add(auditTreeRecord.getValueInteger("nodeAuditTableId"));
|
||||
auditRecordFilter.addSubFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("auditTableId", QCriteriaOperator.EQUALS, auditTreeRecord.getValue("nodeAuditTableId")))
|
||||
.withCriteria(new QFilterCriteria("recordId", QCriteriaOperator.EQUALS, auditTreeRecord.getValue("nodeRecordId"))));
|
||||
}
|
||||
}
|
||||
////////////////////////////////////////////////
|
||||
// todo - reverse (e.g., for child, non-root) //
|
||||
////////////////////////////////////////////////
|
||||
|
||||
///////////////////////
|
||||
// select the audits //
|
||||
///////////////////////
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(AuditsMetaDataProvider.TABLE_NAME_AUDIT);
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.setIncludeAssociations(true);
|
||||
queryInput.setFilter(new QQueryFilter()
|
||||
.withLimit(limit)
|
||||
.withSubFilters(List.of(auditRecordFilter))
|
||||
.withOrderBy(new QFilterOrderBy("timestamp", "asc".equals(sortDirection)))
|
||||
);
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
ArrayList<QRecord> audits = CollectionUtils.useOrWrap(queryOutput.getRecords(), new TypeToken<>() {}); // todo - share simple-class format change, verify in checkstyle
|
||||
runBackendStepOutput.addValue("audits", audits);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// look up count if needed, else use audit list size //
|
||||
///////////////////////////////////////////////////////
|
||||
if(audits.size() >= limit)
|
||||
{
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(AuditsMetaDataProvider.TABLE_NAME_AUDIT);
|
||||
countInput.setFilter(new QQueryFilter().withSubFilters(List.of(auditRecordFilter)));
|
||||
CountOutput countOutput = new CountAction().execute(countInput);
|
||||
runBackendStepOutput.addValue("count", countOutput.getCount());
|
||||
}
|
||||
else
|
||||
{
|
||||
runBackendStepOutput.addValue("count", audits.size());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// put map of auditTableId to table names, and table name to (full) table meta data in result //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(otherAuditTableIds))
|
||||
{
|
||||
QueryInput auditTableQueryInput = new QueryInput();
|
||||
auditTableQueryInput.setTableName(AuditsMetaDataProvider.TABLE_NAME_AUDIT_TABLE);
|
||||
auditTableQueryInput.setFilter(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.IN, new ArrayList<>(otherAuditTableIds))));
|
||||
QueryOutput auditTableQueryOutput = new QueryAction().execute(auditTableQueryInput);
|
||||
|
||||
HashMap<Integer, QRecord> auditTableMap = auditTableQueryOutput.getRecords().stream().collect(Collectors.toMap(r -> r.getValueInteger("id"), r -> r, (a, b) -> a, HashMap::new));
|
||||
runBackendStepOutput.addValue("auditTableMap", auditTableMap);
|
||||
|
||||
HashMap<String, QFrontendTableMetaData> tableMetaDataMap = new HashMap<>();
|
||||
for(QRecord auditTable : auditTableMap.values())
|
||||
{
|
||||
String subTableName = auditTable.getValueString("name");
|
||||
|
||||
TableMetaDataInput tableMetaDataInput = new TableMetaDataInput();
|
||||
tableMetaDataInput.setTableName(subTableName);
|
||||
TableMetaDataOutput tableMetaDataOutput = new TableMetaDataAction().execute(tableMetaDataInput);
|
||||
|
||||
tableMetaDataMap.put(subTableName, tableMetaDataOutput.getTable());
|
||||
}
|
||||
|
||||
runBackendStepOutput.addValue("tableMetaDataMap", tableMetaDataMap);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QException("Error getting record audits", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -23,6 +23,8 @@ package com.kingsrook.qqq.backend.core.utils;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@ -34,6 +36,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
@ -550,4 +553,27 @@ public class CollectionUtils
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For cases where you have a Collection (of an unknown type), and you know you
|
||||
** want/need it in a specific concrete type (say, ArrayList), but you don't want
|
||||
** to just blindly copy it (e.g., as that may be expensive), call this method.
|
||||
**
|
||||
** ArrayList<String> myStrings = CollectionUtils.useOrWrap(yourStrings, new TypeToken<>() {});
|
||||
**
|
||||
** Note that you may always just pass `new TypeToken() {}` as the 2nd arg - then
|
||||
** the compiler will infer the type (T) based on the variable you're assigning into.
|
||||
*******************************************************************************/
|
||||
public static <E, T extends Collection<E>> T useOrWrap(Collection<E> collection, TypeToken<T> typeToken) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException
|
||||
{
|
||||
Class<T> targetClass = (Class<T>) typeToken.getRawType();
|
||||
if(targetClass.isInstance(collection))
|
||||
{
|
||||
return (targetClass.cast(collection));
|
||||
}
|
||||
|
||||
Constructor<T> constructor = targetClass.getConstructor(Collection.class);
|
||||
return (constructor.newInstance(collection));
|
||||
}
|
||||
}
|
||||
|
@ -22,18 +22,23 @@
|
||||
package com.kingsrook.qqq.backend.core.utils;
|
||||
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@ -544,4 +549,26 @@ class CollectionUtilsTest extends BaseTest
|
||||
assertEquals(List.of(1, 2, 3), CollectionUtils.mergeLists(null, List.of(1, 2, 3), null));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException
|
||||
{
|
||||
{
|
||||
List<String> originalList = new ArrayList<>(List.of("A", "B", "C"));
|
||||
ArrayList<String> reallyArrayList = CollectionUtils.useOrWrap(originalList, new TypeToken<>() {});
|
||||
assertSame(originalList, reallyArrayList);
|
||||
}
|
||||
|
||||
{
|
||||
List<String> originalList = new LinkedList<>(List.of("A", "B", "C"));
|
||||
ArrayList<String> reallyArrayList = CollectionUtils.useOrWrap(originalList, new TypeToken<>() {});
|
||||
assertNotSame(originalList, reallyArrayList);
|
||||
assertEquals(ArrayList.class, reallyArrayList.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user