diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/CustomizerLoader.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/CustomizerLoader.java
new file mode 100644
index 00000000..d0ef11d8
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/CustomizerLoader.java
@@ -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 .
+ */
+
+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 codeReference = table.getCustomizer(customizerName);
+ if(codeReference.isPresent())
+ {
+ return (CustomizerLoader.getFunction(codeReference.get()));
+ }
+
+ return null;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @SuppressWarnings("unchecked")
+ public static Function 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) 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);
+ }
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/Customizers.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/Customizers.java
new file mode 100644
index 00000000..4da39af3
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/Customizers.java
@@ -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 .
+ */
+
+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";
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QInstanceValidationException.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QInstanceValidationException.java
index 311ec0a8..fa090ca1 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QInstanceValidationException.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QInstanceValidationException.java
@@ -57,7 +57,7 @@ public class QInstanceValidationException extends QException
{
super(
(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");
if(reasons != null && reasons.size() > 0)
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QueryOutput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QueryOutput.java
index a9e19342..96340a69 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QueryOutput.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QueryOutput.java
@@ -24,8 +24,13 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.io.Serializable;
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.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
{
+ private static final Logger LOG = LogManager.getLogger(QueryOutput.class);
+
private QueryOutputStorageInterface storage;
+ private Function postQueryRecordCustomizer;
+
/*******************************************************************************
@@ -52,6 +61,8 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
{
storage = new QueryOutputList();
}
+
+ postQueryRecordCustomizer = (Function) CustomizerLoader.getTableCustomizerFunction(queryInput.getTable(), Customizers.POST_QUERY_RECORD);
}
@@ -65,16 +76,36 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
*******************************************************************************/
public void addRecord(QRecord record)
{
+ record = runPostQueryRecordCustomizer(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
*******************************************************************************/
public void addRecords(List records)
{
+ if(this.postQueryRecordCustomizer != null)
+ {
+ records.replaceAll(t -> this.postQueryRecordCustomizer.apply(t));
+ }
+
storage.addRecords(records);
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java
index 0fec3d48..2f920886 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java
@@ -408,12 +408,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
}
QCodeReference function = customizers.get(customizerName);
- if(function == null)
- {
- throw (new IllegalArgumentException("Customizer [" + customizerName + "] was not found in table [" + name + "]."));
- }
-
- return (Optional.of(function));
+ return (Optional.ofNullable(function));
}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java
index f07e1b9a..ec1b2673 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java
@@ -23,6 +23,8 @@ package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory;
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.DeleteAction;
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.data.QRecord;
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.session.QSession;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
@@ -90,28 +94,9 @@ class MemoryBackendModuleTest
InsertInput insertInput = new InsertInput(qInstance);
insertInput.setSession(session);
insertInput.setTableName(table.getName());
- insertInput.setRecords(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)
- ));
+ insertInput.setRecords(getTestRecords(table));
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().anyMatch(r -> r.getValueInteger("id").equals(1)));
assertTrue(insertOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2)));
@@ -124,7 +109,7 @@ class MemoryBackendModuleTest
queryInput.setSession(session);
queryInput.setTableName(table.getName());
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().anyMatch(r -> r.getValueInteger("id").equals(1)));
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(2)));
@@ -152,10 +137,10 @@ class MemoryBackendModuleTest
.withValue("type", "ellipse")
));
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
- assertEquals(updateOutput.getRecords().size(), 2);
+ assertEquals(2, updateOutput.getRecords().size());
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().anyMatch(r -> r.getValueString("name").equals("Not My Triangle any more")));
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueString("type").equals("ellipse")));
@@ -171,15 +156,104 @@ class MemoryBackendModuleTest
deleteInput.setTableName(table.getName());
deleteInput.setPrimaryKeys(List.of(1, 2));
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
- assertEquals(deleteOutput.getDeletedRecordCount(), 2);
+ assertEquals(2, deleteOutput.getDeletedRecordCount());
assertEquals(1, new CountAction().execute(countInput).getCount());
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(2)));
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValueInteger("id").equals(3)));
}
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private List 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
+ {
+ static int executionCount = 0;
+
+
+
+ @Override
+ public QRecord apply(QRecord record)
+ {
+ executionCount++;
+ record.setValue("tenTimesId", record.getValueInteger("id") * 10);
+ return (record);
+ }
+ }
}
\ No newline at end of file
diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/FilesystemBackendModuleInterface.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/FilesystemBackendModuleInterface.java
index 23c38c46..816caa06 100644
--- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/FilesystemBackendModuleInterface.java
+++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/FilesystemBackendModuleInterface.java
@@ -31,11 +31,11 @@ import com.kingsrook.qqq.backend.module.filesystem.base.actions.AbstractBaseFile
*******************************************************************************/
public interface FilesystemBackendModuleInterface
{
- String CUSTOMIZER_FILE_POST_FILE_READ = "postFileRead";
/*******************************************************************************
** For filesystem backends, get the module-specific action base-class, that helps
** with functions like listing and deleting files.
*******************************************************************************/
AbstractBaseFilesystemAction getActionBase();
+
}
diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java
index 02b26622..5f846dea 100644
--- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java
+++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java
@@ -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.QTableMetaData;
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.model.metadata.AbstractFilesystemBackendMetaData;
import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.AbstractFilesystemTableBackendDetails;
@@ -203,7 +202,15 @@ public abstract class AbstractBaseFilesystemAction
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
{
@@ -281,7 +288,7 @@ public abstract class AbstractBaseFilesystemAction
*******************************************************************************/
private String customizeFileContentsAfterReading(QTableMetaData table, String fileContents) throws QException
{
- Optional optionalCustomizer = table.getCustomizer(FilesystemBackendModuleInterface.CUSTOMIZER_FILE_POST_FILE_READ);
+ Optional optionalCustomizer = table.getCustomizer(FilesystemCustomizers.POST_READ_FILE);
if(optionalCustomizer.isEmpty())
{
return (fileContents);
diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/FilesystemCustomizers.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/FilesystemCustomizers.java
new file mode 100644
index 00000000..8e2416e0
--- /dev/null
+++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/FilesystemCustomizers.java
@@ -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 .
+ */
+
+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";
+}
diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/FilesystemBackendModule.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/FilesystemBackendModule.java
index 2bc14244..493a08ac 100644
--- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/FilesystemBackendModule.java
+++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/FilesystemBackendModule.java
@@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.module.filesystem.local;
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.InsertInterface;
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.actions.AbstractBaseFilesystemAction;
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.FilesystemInsertAction;
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();
+ }
+
+
/*******************************************************************************
**
diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemCountAction.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemCountAction.java
new file mode 100644
index 00000000..586eb9f8
--- /dev/null
+++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemCountAction.java
@@ -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 .
+ */
+
+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);
+ }
+
+}
diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemQueryActionTest.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemQueryActionTest.java
index 90771e43..be40e86a 100644
--- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemQueryActionTest.java
+++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemQueryActionTest.java
@@ -26,14 +26,13 @@ import java.util.function.Function;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
-import com.kingsrook.qqq.backend.core.model.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.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.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.actions.FilesystemCustomizers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -72,10 +71,7 @@ public class FilesystemQueryActionTest extends FilesystemActionTest
QInstance instance = TestUtils.defineInstance();
QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_LOCAL_FS_JSON);
- table.withCustomizer(FilesystemBackendModuleInterface.CUSTOMIZER_FILE_POST_FILE_READ, new QCodeReference()
- .withName(ValueUpshifter.class.getName())
- .withCodeType(QCodeType.JAVA)
- .withCodeUsage(QCodeUsage.CUSTOMIZER));
+ table.withCustomizer(FilesystemCustomizers.POST_READ_FILE, new QCodeReference(ValueUpshifter.class, QCodeUsage.CUSTOMIZER));
queryInput.setInstance(instance);
queryInput.setTableName(TestUtils.defineLocalFilesystemJSONPersonTable().getName());