mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Adding POST_QUERY_RECORD customizer; formalizing customizers a bit more
This commit is contained in:
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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.customizers;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Utility to load code for running QQQ customizers.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class CustomizerLoader
|
||||||
|
{
|
||||||
|
private static final Logger LOG = LogManager.getLogger(CustomizerLoader.class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static Function<?, ?> getTableCustomizerFunction(QTableMetaData table, String customizerName)
|
||||||
|
{
|
||||||
|
Optional<QCodeReference> codeReference = table.getCustomizer(customizerName);
|
||||||
|
if(codeReference.isPresent())
|
||||||
|
{
|
||||||
|
return (CustomizerLoader.getFunction(codeReference.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T, R> Function<T, R> getFunction(QCodeReference codeReference)
|
||||||
|
{
|
||||||
|
if(codeReference == null)
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
throw (new IllegalArgumentException("Only JAVA customizers are supported at this time."));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class<?> customizerClass = Class.forName(codeReference.getName());
|
||||||
|
return ((Function<T, R>) customizerClass.getConstructor().newInstance());
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.error("Error initializing customizer: " + codeReference);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
||||||
|
// as we'll want to validate all functions in the instance validator at startup time (and IT will throw //
|
||||||
|
// if it finds an invalid code reference //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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.customizers;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Standard place where the names of QQQ Customization points are defined.
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface Customizers
|
||||||
|
{
|
||||||
|
String POST_QUERY_RECORD = "postQueryRecord";
|
||||||
|
|
||||||
|
}
|
@ -57,7 +57,7 @@ public class QInstanceValidationException extends QException
|
|||||||
{
|
{
|
||||||
super(
|
super(
|
||||||
(reasons != null && reasons.size() > 0)
|
(reasons != null && reasons.size() > 0)
|
||||||
? "Instance validation failed for the following reasons: " + StringUtils.joinWithCommasAndAnd(reasons)
|
? "Instance validation failed for the following reasons:\n - " + StringUtils.join("\n - ", reasons)
|
||||||
: "Validation failed, but no reasons were provided");
|
: "Validation failed, but no reasons were provided");
|
||||||
|
|
||||||
if(reasons != null && reasons.size() > 0)
|
if(reasons != null && reasons.size() > 0)
|
||||||
|
@ -24,8 +24,13 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.CustomizerLoader;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.Customizers;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -34,8 +39,12 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QueryOutput extends AbstractActionOutput implements Serializable
|
public class QueryOutput extends AbstractActionOutput implements Serializable
|
||||||
{
|
{
|
||||||
|
private static final Logger LOG = LogManager.getLogger(QueryOutput.class);
|
||||||
|
|
||||||
private QueryOutputStorageInterface storage;
|
private QueryOutputStorageInterface storage;
|
||||||
|
|
||||||
|
private Function<QRecord, QRecord> postQueryRecordCustomizer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -52,6 +61,8 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
|
|||||||
{
|
{
|
||||||
storage = new QueryOutputList();
|
storage = new QueryOutputList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postQueryRecordCustomizer = (Function<QRecord, QRecord>) CustomizerLoader.getTableCustomizerFunction(queryInput.getTable(), Customizers.POST_QUERY_RECORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -65,16 +76,36 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void addRecord(QRecord record)
|
public void addRecord(QRecord record)
|
||||||
{
|
{
|
||||||
|
record = runPostQueryRecordCustomizer(record);
|
||||||
storage.addRecord(record);
|
storage.addRecord(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QRecord runPostQueryRecordCustomizer(QRecord record)
|
||||||
|
{
|
||||||
|
if(this.postQueryRecordCustomizer != null)
|
||||||
|
{
|
||||||
|
record = this.postQueryRecordCustomizer.apply(record);
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** add a list of records to this output
|
** add a list of records to this output
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void addRecords(List<QRecord> records)
|
public void addRecords(List<QRecord> records)
|
||||||
{
|
{
|
||||||
|
if(this.postQueryRecordCustomizer != null)
|
||||||
|
{
|
||||||
|
records.replaceAll(t -> this.postQueryRecordCustomizer.apply(t));
|
||||||
|
}
|
||||||
|
|
||||||
storage.addRecords(records);
|
storage.addRecords(records);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,12 +408,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
QCodeReference function = customizers.get(customizerName);
|
QCodeReference function = customizers.get(customizerName);
|
||||||
if(function == null)
|
return (Optional.ofNullable(function));
|
||||||
{
|
|
||||||
throw (new IllegalArgumentException("Customizer [" + customizerName + "] was not found in table [" + name + "]."));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (Optional.of(function));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory;
|
|||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.Customizers;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
@ -40,6 +42,8 @@ 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.actions.tables.update.UpdateOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
@ -90,28 +94,9 @@ class MemoryBackendModuleTest
|
|||||||
InsertInput insertInput = new InsertInput(qInstance);
|
InsertInput insertInput = new InsertInput(qInstance);
|
||||||
insertInput.setSession(session);
|
insertInput.setSession(session);
|
||||||
insertInput.setTableName(table.getName());
|
insertInput.setTableName(table.getName());
|
||||||
insertInput.setRecords(List.of(
|
insertInput.setRecords(getTestRecords(table));
|
||||||
new QRecord()
|
|
||||||
.withTableName(table.getName())
|
|
||||||
.withValue("name", "My Triangle")
|
|
||||||
.withValue("type", "triangle")
|
|
||||||
.withValue("noOfSides", 3)
|
|
||||||
.withValue("isPolygon", true),
|
|
||||||
new QRecord()
|
|
||||||
.withTableName(table.getName())
|
|
||||||
.withValue("name", "Your Square")
|
|
||||||
.withValue("type", "square")
|
|
||||||
.withValue("noOfSides", 4)
|
|
||||||
.withValue("isPolygon", true),
|
|
||||||
new QRecord()
|
|
||||||
.withTableName(table.getName())
|
|
||||||
.withValue("name", "Some Circle")
|
|
||||||
.withValue("type", "circle")
|
|
||||||
.withValue("noOfSides", null)
|
|
||||||
.withValue("isPolygon", false)
|
|
||||||
));
|
|
||||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||||
assertEquals(insertOutput.getRecords().size(), 3);
|
assertEquals(3, insertOutput.getRecords().size());
|
||||||
assertTrue(insertOutput.getRecords().stream().allMatch(r -> r.getValue("id") != null));
|
assertTrue(insertOutput.getRecords().stream().allMatch(r -> r.getValue("id") != null));
|
||||||
assertTrue(insertOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1)));
|
assertTrue(insertOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1)));
|
||||||
assertTrue(insertOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2)));
|
assertTrue(insertOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2)));
|
||||||
@ -124,7 +109,7 @@ class MemoryBackendModuleTest
|
|||||||
queryInput.setSession(session);
|
queryInput.setSession(session);
|
||||||
queryInput.setTableName(table.getName());
|
queryInput.setTableName(table.getName());
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
assertEquals(queryOutput.getRecords().size(), 3);
|
assertEquals(3, queryOutput.getRecords().size());
|
||||||
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValue("id") != null));
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValue("id") != null));
|
||||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1)));
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1)));
|
||||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2)));
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2)));
|
||||||
@ -152,10 +137,10 @@ class MemoryBackendModuleTest
|
|||||||
.withValue("type", "ellipse")
|
.withValue("type", "ellipse")
|
||||||
));
|
));
|
||||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||||
assertEquals(updateOutput.getRecords().size(), 2);
|
assertEquals(2, updateOutput.getRecords().size());
|
||||||
|
|
||||||
queryOutput = new QueryAction().execute(queryInput);
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
assertEquals(queryOutput.getRecords().size(), 3);
|
assertEquals(3, queryOutput.getRecords().size());
|
||||||
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("name").equals("My Triangle")));
|
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("name").equals("My Triangle")));
|
||||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("name").equals("Not My Triangle any more")));
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("name").equals("Not My Triangle any more")));
|
||||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("type").equals("ellipse")));
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("type").equals("ellipse")));
|
||||||
@ -171,15 +156,104 @@ class MemoryBackendModuleTest
|
|||||||
deleteInput.setTableName(table.getName());
|
deleteInput.setTableName(table.getName());
|
||||||
deleteInput.setPrimaryKeys(List.of(1, 2));
|
deleteInput.setPrimaryKeys(List.of(1, 2));
|
||||||
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
|
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
|
||||||
assertEquals(deleteOutput.getDeletedRecordCount(), 2);
|
assertEquals(2, deleteOutput.getDeletedRecordCount());
|
||||||
|
|
||||||
assertEquals(1, new CountAction().execute(countInput).getCount());
|
assertEquals(1, new CountAction().execute(countInput).getCount());
|
||||||
|
|
||||||
queryOutput = new QueryAction().execute(queryInput);
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
assertEquals(queryOutput.getRecords().size(), 1);
|
assertEquals(1, queryOutput.getRecords().size());
|
||||||
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueInteger("id").equals(1)));
|
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueInteger("id").equals(1)));
|
||||||
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueInteger("id").equals(2)));
|
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueInteger("id").equals(2)));
|
||||||
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3)));
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private List<QRecord> getTestRecords(QTableMetaData table)
|
||||||
|
{
|
||||||
|
return List.of(
|
||||||
|
new QRecord()
|
||||||
|
.withTableName(table.getName())
|
||||||
|
.withValue("name", "My Triangle")
|
||||||
|
.withValue("type", "triangle")
|
||||||
|
.withValue("noOfSides", 3)
|
||||||
|
.withValue("isPolygon", true),
|
||||||
|
new QRecord()
|
||||||
|
.withTableName(table.getName())
|
||||||
|
.withValue("name", "Your Square")
|
||||||
|
.withValue("type", "square")
|
||||||
|
.withValue("noOfSides", 4)
|
||||||
|
.withValue("isPolygon", true),
|
||||||
|
new QRecord()
|
||||||
|
.withTableName(table.getName())
|
||||||
|
.withValue("name", "Some Circle")
|
||||||
|
.withValue("type", "circle")
|
||||||
|
.withValue("noOfSides", null)
|
||||||
|
.withValue("isPolygon", false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testCustomizer() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE);
|
||||||
|
QSession session = new QSession();
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// add a customizer to the table //
|
||||||
|
///////////////////////////////////
|
||||||
|
table.withCustomizer(Customizers.POST_QUERY_RECORD, new QCodeReference(ShapeTestCustomizer.class, QCodeUsage.CUSTOMIZER));
|
||||||
|
|
||||||
|
//////////////////
|
||||||
|
// do an insert //
|
||||||
|
//////////////////
|
||||||
|
InsertInput insertInput = new InsertInput(qInstance);
|
||||||
|
insertInput.setSession(session);
|
||||||
|
insertInput.setTableName(table.getName());
|
||||||
|
insertInput.setRecords(getTestRecords(table));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
// do a query - assert that the customizer did stuff //
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
ShapeTestCustomizer.executionCount = 0;
|
||||||
|
QueryInput queryInput = new QueryInput(qInstance);
|
||||||
|
queryInput.setSession(session);
|
||||||
|
queryInput.setTableName(table.getName());
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(3, queryOutput.getRecords().size());
|
||||||
|
assertEquals(3, ShapeTestCustomizer.executionCount);
|
||||||
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(1) && r.getValueInteger("tenTimesId").equals(10)));
|
||||||
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2) && r.getValueInteger("tenTimesId").equals(20)));
|
||||||
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3) && r.getValueInteger("tenTimesId").equals(30)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class ShapeTestCustomizer implements Function<QRecord, QRecord>
|
||||||
|
{
|
||||||
|
static int executionCount = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QRecord apply(QRecord record)
|
||||||
|
{
|
||||||
|
executionCount++;
|
||||||
|
record.setValue("tenTimesId", record.getValueInteger("id") * 10);
|
||||||
|
return (record);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -31,11 +31,11 @@ import com.kingsrook.qqq.backend.module.filesystem.base.actions.AbstractBaseFile
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public interface FilesystemBackendModuleInterface<FILE>
|
public interface FilesystemBackendModuleInterface<FILE>
|
||||||
{
|
{
|
||||||
String CUSTOMIZER_FILE_POST_FILE_READ = "postFileRead";
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** For filesystem backends, get the module-specific action base-class, that helps
|
** For filesystem backends, get the module-specific action base-class, that helps
|
||||||
** with functions like listing and deleting files.
|
** with functions like listing and deleting files.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
AbstractBaseFilesystemAction<FILE> getActionBase();
|
AbstractBaseFilesystemAction<FILE> getActionBase();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemBackendModuleInterface;
|
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemRecordBackendDetailFields;
|
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemRecordBackendDetailFields;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.AbstractFilesystemBackendMetaData;
|
import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.AbstractFilesystemBackendMetaData;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.AbstractFilesystemTableBackendDetails;
|
import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.AbstractFilesystemTableBackendDetails;
|
||||||
@ -203,7 +202,15 @@ public abstract class AbstractBaseFilesystemAction<FILE>
|
|||||||
|
|
||||||
if(queryInput.getRecordPipe() != null)
|
if(queryInput.getRecordPipe() != null)
|
||||||
{
|
{
|
||||||
new CsvToQRecordAdapter().buildRecordsFromCsv(queryInput.getRecordPipe(), fileContents, table, null, (record -> addBackendDetailsToRecord(record, file)));
|
new CsvToQRecordAdapter().buildRecordsFromCsv(queryInput.getRecordPipe(), fileContents, table, null, (record ->
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// since the CSV adapter is the one responsible for putting records into the pipe (rather than the queryOutput), //
|
||||||
|
// we must do some of QueryOutput's normal job here - and run the runPostQueryRecordCustomizer //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
addBackendDetailsToRecord(record, file);
|
||||||
|
queryOutput.runPostQueryRecordCustomizer(record);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -281,7 +288,7 @@ public abstract class AbstractBaseFilesystemAction<FILE>
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private String customizeFileContentsAfterReading(QTableMetaData table, String fileContents) throws QException
|
private String customizeFileContentsAfterReading(QTableMetaData table, String fileContents) throws QException
|
||||||
{
|
{
|
||||||
Optional<QCodeReference> optionalCustomizer = table.getCustomizer(FilesystemBackendModuleInterface.CUSTOMIZER_FILE_POST_FILE_READ);
|
Optional<QCodeReference> optionalCustomizer = table.getCustomizer(FilesystemCustomizers.POST_READ_FILE);
|
||||||
if(optionalCustomizer.isEmpty())
|
if(optionalCustomizer.isEmpty())
|
||||||
{
|
{
|
||||||
return (fileContents);
|
return (fileContents);
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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.module.filesystem.base.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.Customizers;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Standard place where the names of QQQ Customization points for filesystem-based
|
||||||
|
** backends are defined.
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface FilesystemCustomizers extends Customizers
|
||||||
|
{
|
||||||
|
String POST_READ_FILE = "postReadFile";
|
||||||
|
}
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.module.filesystem.local;
|
|||||||
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||||
@ -33,6 +34,7 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
|||||||
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemBackendModuleInterface;
|
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemBackendModuleInterface;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.base.actions.AbstractBaseFilesystemAction;
|
import com.kingsrook.qqq.backend.module.filesystem.base.actions.AbstractBaseFilesystemAction;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.local.actions.AbstractFilesystemAction;
|
import com.kingsrook.qqq.backend.module.filesystem.local.actions.AbstractFilesystemAction;
|
||||||
|
import com.kingsrook.qqq.backend.module.filesystem.local.actions.FilesystemCountAction;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.local.actions.FilesystemDeleteAction;
|
import com.kingsrook.qqq.backend.module.filesystem.local.actions.FilesystemDeleteAction;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.local.actions.FilesystemInsertAction;
|
import com.kingsrook.qqq.backend.module.filesystem.local.actions.FilesystemInsertAction;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.local.actions.FilesystemQueryAction;
|
import com.kingsrook.qqq.backend.module.filesystem.local.actions.FilesystemQueryAction;
|
||||||
@ -107,6 +109,16 @@ public class FilesystemBackendModule implements QBackendModuleInterface, Filesys
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public CountInterface getCountInterface()
|
||||||
|
{
|
||||||
|
return new FilesystemCountAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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.module.filesystem.local.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
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.QueryInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class FilesystemCountAction extends AbstractFilesystemAction implements CountInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public CountOutput execute(CountInput countInput) throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = new QueryInput(countInput.getInstance());
|
||||||
|
queryInput.setSession(countInput.getSession());
|
||||||
|
queryInput.setTableName(countInput.getTableName());
|
||||||
|
queryInput.setFilter(countInput.getFilter());
|
||||||
|
QueryOutput queryOutput = executeQuery(queryInput);
|
||||||
|
|
||||||
|
CountOutput countOutput = new CountOutput();
|
||||||
|
countOutput.setCount(queryOutput.getRecords().size());
|
||||||
|
return (countOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -26,14 +26,13 @@ import java.util.function.Function;
|
|||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
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.code.QCodeUsage;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
|
import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemBackendModuleInterface;
|
|
||||||
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemRecordBackendDetailFields;
|
import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemRecordBackendDetailFields;
|
||||||
|
import com.kingsrook.qqq.backend.module.filesystem.base.actions.FilesystemCustomizers;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@ -72,10 +71,7 @@ public class FilesystemQueryActionTest extends FilesystemActionTest
|
|||||||
QInstance instance = TestUtils.defineInstance();
|
QInstance instance = TestUtils.defineInstance();
|
||||||
|
|
||||||
QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_LOCAL_FS_JSON);
|
QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_LOCAL_FS_JSON);
|
||||||
table.withCustomizer(FilesystemBackendModuleInterface.CUSTOMIZER_FILE_POST_FILE_READ, new QCodeReference()
|
table.withCustomizer(FilesystemCustomizers.POST_READ_FILE, new QCodeReference(ValueUpshifter.class, QCodeUsage.CUSTOMIZER));
|
||||||
.withName(ValueUpshifter.class.getName())
|
|
||||||
.withCodeType(QCodeType.JAVA)
|
|
||||||
.withCodeUsage(QCodeUsage.CUSTOMIZER));
|
|
||||||
|
|
||||||
queryInput.setInstance(instance);
|
queryInput.setInstance(instance);
|
||||||
queryInput.setTableName(TestUtils.defineLocalFilesystemJSONPersonTable().getName());
|
queryInput.setTableName(TestUtils.defineLocalFilesystemJSONPersonTable().getName());
|
||||||
|
Reference in New Issue
Block a user