Checkpoint - child audits (audit tree)

This commit is contained in:
2023-06-08 15:46:09 -05:00
parent b75fd29a57
commit c0ffd2fe0e
7 changed files with 473 additions and 8 deletions

View File

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

View File

@ -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());
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}
}

View File

@ -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());
}
}
}