diff --git a/.circleci/config.yml b/.circleci/config.yml
index 0ef02745..40a1a443 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,3 +1,6 @@
+## Deviations from qqq-java library standard circleci config:
+## - To test AWS S3, uses localstsack executor, orb, and startup step
+## - This docker image doesn't have java-17, so we install (and cache) jvm-17
version: 2.1
executors:
@@ -8,8 +11,18 @@ executors:
orbs:
slack: circleci/slack@4.10.1
+ localstack: localstack/platform@1.0
commands:
+ install_java17:
+ steps:
+ - run:
+ name: Install Java 17
+ command: |
+ sudo add-apt-repository -y ppa:openjdk-r/ppa
+ sudo apt install -y openjdk-17-jdk
+ sudo rm /etc/alternatives/java
+ sudo ln -s /usr/lib/jvm/java-17-openjdk-amd64/bin/java /etc/alternatives/java
run_maven:
parameters:
maven_subcommand:
@@ -39,16 +52,20 @@ commands:
jobs:
mvn_test:
- executor: java17
+ executor: localstack/default
steps:
+ - localstack/startup
+ - install_java17
- run_maven:
maven_subcommand: test
- slack/notify:
event: fail
mvn_deploy:
- executor: java17
+ executor: localstack/default
steps:
+ - localstack/startup
+ - install_java17
- run_maven:
maven_subcommand: deploy
- slack/notify:
diff --git a/pom.xml b/pom.xml
index 51e09ceb..83edccf9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,7 +25,7 @@
com.kingsrook.qqqqqq-backend-module-filesystem
- 0.0.0-SNAPSHOT
+ 0.0.0scm:git:git@github.com:Kingsrook/qqq-backend-module-rdbms.git
@@ -51,7 +51,7 @@
com.kingsrook.qqqqqq-backend-core
- 0.0.0-SNAPSHOT
+ 0.0.0
@@ -61,21 +61,9 @@
1.12.243
- org.testcontainers
- testcontainers
- 1.17.2
- test
-
-
- org.testcontainers
- localstack
- 1.17.2
- test
-
-
- org.testcontainers
- junit-jupiter
- 1.17.2
+ cloud.localstack
+ localstack-utils
+ 0.2.20test
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/FilesystemBackendModuleInterface.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/FilesystemBackendModuleInterface.java
new file mode 100644
index 00000000..3051e18a
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/FilesystemBackendModuleInterface.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+
+import com.kingsrook.qqq.backend.module.filesystem.base.actions.AbstractBaseFilesystemAction;
+
+
+/*******************************************************************************
+ ** Interface to add additional functionality commonly among the various filesystem
+ ** module implementations.
+ *******************************************************************************/
+public interface FilesystemBackendModuleInterface
+{
+ /*******************************************************************************
+ ** For filesystem backends, get the module-specific action base-class, that helps
+ ** with functions like listing and deleting files.
+ *******************************************************************************/
+ AbstractBaseFilesystemAction getActionBase();
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/FilesystemRecordBackendDetailFields.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/FilesystemRecordBackendDetailFields.java
new file mode 100644
index 00000000..415aa06f
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/FilesystemRecordBackendDetailFields.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.module.filesystem.base;
+
+
+/*******************************************************************************
+ ** Define the field names (keys) to be used in the backendDetails structure
+ ** under Records from this (these) modules.
+ *******************************************************************************/
+public interface FilesystemRecordBackendDetailFields
+{
+ String FULL_PATH = "fullPath";
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java
new file mode 100644
index 00000000..9a04f289
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/actions/AbstractBaseFilesystemAction.java
@@ -0,0 +1,294 @@
+/*
+ * 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 java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import com.kingsrook.qqq.backend.core.adapters.CsvToQRecordAdapter;
+import com.kingsrook.qqq.backend.core.adapters.JsonToQRecordAdapter;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.query.QueryRequest;
+import com.kingsrook.qqq.backend.core.model.actions.query.QueryResult;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.QCodeReference;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.QTableBackendDetails;
+import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
+import com.kingsrook.qqq.backend.core.utils.StringUtils;
+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;
+import com.kingsrook.qqq.backend.module.filesystem.exceptions.FilesystemException;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.NotImplementedException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+
+/*******************************************************************************
+ ** Base class for all Filesystem actions across all modules.
+ **
+ ** @param FILE The class that represents a file in the sub-module. e.g.,
+ * a java.io.File, or an S3Object.
+ *******************************************************************************/
+public abstract class AbstractBaseFilesystemAction
+{
+ private static final Logger LOG = LogManager.getLogger(AbstractBaseFilesystemAction.class);
+
+
+
+ /*******************************************************************************
+ ** List the files for a table - to be implemented in module-specific subclasses.
+ *******************************************************************************/
+ public abstract List listFiles(QTableMetaData table, QBackendMetaData backendBase);
+
+ /*******************************************************************************
+ ** Read the contents of a file - to be implemented in module-specific subclasses.
+ *******************************************************************************/
+ public abstract InputStream readFile(FILE file) throws IOException;
+
+ /*******************************************************************************
+ ** Write a file - to be implemented in module-specific subclasses.
+ *******************************************************************************/
+ public abstract void writeFile(QBackendMetaData backend, String path, byte[] contents) throws IOException;
+
+ /*******************************************************************************
+ ** Get a string that represents the full path to a file.
+ *******************************************************************************/
+ public abstract String getFullPathForFile(FILE file);
+
+ /*******************************************************************************
+ ** In contrast with the DeleteAction, which deletes RECORDS - this is a
+ ** filesystem-(or s3, sftp, etc)-specific extension to delete an entire FILE
+ ** e.g., for post-ETL.
+ **
+ ** @throws FilesystemException if the delete is known to have failed, and the file is thought to still exit
+ *******************************************************************************/
+ public abstract void deleteFile(QInstance instance, QTableMetaData table, String fileReference) throws FilesystemException;
+
+ /*******************************************************************************
+ ** Move a file from a source path, to a destination path.
+ **
+ ** @throws FilesystemException if the move is known to have failed
+ *******************************************************************************/
+ public abstract void moveFile(QInstance instance, QTableMetaData table, String source, String destination) throws FilesystemException;
+
+ /*******************************************************************************
+ ** e.g., with a base path of /foo/
+ ** and a table path of /bar/
+ ** and a file at /foo/bar/baz.txt
+ ** give us just the baz.txt part.
+ *******************************************************************************/
+ public abstract String stripBackendAndTableBasePathsFromFileName(String filePath, QBackendMetaData sourceBackend, QTableMetaData sourceTable);
+
+
+
+ /*******************************************************************************
+ ** Append together the backend's base path (if present), with a table's base
+ ** path (again, if present).
+ *******************************************************************************/
+ public String getFullBasePath(QTableMetaData table, QBackendMetaData backendBase)
+ {
+ AbstractFilesystemBackendMetaData metaData = getBackendMetaData(AbstractFilesystemBackendMetaData.class, backendBase);
+ String fullPath = StringUtils.hasContent(metaData.getBasePath()) ? metaData.getBasePath() : "";
+
+ AbstractFilesystemTableBackendDetails tableDetails = getTableBackendDetails(AbstractFilesystemTableBackendDetails.class, table);
+ if(StringUtils.hasContent(tableDetails.getBasePath()))
+ {
+ fullPath += File.separatorChar + tableDetails.getBasePath();
+ }
+
+ fullPath += File.separatorChar;
+ fullPath = stripDuplicatedSlashes(fullPath);
+
+ return fullPath;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static String stripDuplicatedSlashes(String path)
+ {
+ if(path == null)
+ {
+ return (null);
+ }
+
+ return (path.replaceAll("//+", "/"));
+ }
+
+
+
+ /*******************************************************************************
+ ** Get the backend metaData, type-checked as the requested type.
+ *******************************************************************************/
+ protected T getBackendMetaData(Class outputClass, QBackendMetaData metaData)
+ {
+ if(!(outputClass.isInstance(metaData)))
+ {
+ throw new IllegalArgumentException("MetaData was not of expected type (was " + metaData.getClass().getSimpleName() + ")");
+ }
+ return outputClass.cast(metaData);
+ }
+
+
+
+ /*******************************************************************************
+ ** Get the backendDetails out of a table, type-checked as the requested type
+ *******************************************************************************/
+ protected T getTableBackendDetails(Class outputClass, QTableMetaData tableMetaData)
+ {
+ QTableBackendDetails tableBackendDetails = tableMetaData.getBackendDetails();
+ if(!(outputClass.isInstance(tableBackendDetails)))
+ {
+ throw new IllegalArgumentException("Table backend details was not of expected type (was " + tableBackendDetails.getClass().getSimpleName() + ")");
+ }
+ return outputClass.cast(tableBackendDetails);
+ }
+
+
+
+ /*******************************************************************************
+ ** Generic implementation of the execute method from the QueryInterface
+ *******************************************************************************/
+ public QueryResult executeQuery(QueryRequest queryRequest) throws QException
+ {
+ preAction(queryRequest.getBackend());
+
+ try
+ {
+ QueryResult rs = new QueryResult();
+ List records = new ArrayList<>();
+ rs.setRecords(records);
+
+ QTableMetaData table = queryRequest.getTable();
+ AbstractFilesystemTableBackendDetails tableDetails = getTableBackendDetails(AbstractFilesystemTableBackendDetails.class, table);
+ List files = listFiles(table, queryRequest.getBackend());
+
+ for(FILE file : files)
+ {
+ LOG.info("Processing file: " + getFullPathForFile(file));
+ switch(tableDetails.getRecordFormat())
+ {
+ case CSV:
+ {
+ String fileContents = IOUtils.toString(readFile(file));
+ fileContents = customizeFileContentsAfterReading(table, fileContents);
+
+ List recordsInFile = new CsvToQRecordAdapter().buildRecordsFromCsv(fileContents, table, null);
+ addBackendDetailsToRecords(recordsInFile, file);
+
+ records.addAll(recordsInFile);
+ break;
+ }
+ case JSON:
+ {
+ String fileContents = IOUtils.toString(readFile(file));
+ fileContents = customizeFileContentsAfterReading(table, fileContents);
+
+ List recordsInFile = new JsonToQRecordAdapter().buildRecordsFromJson(fileContents, table, null);
+ addBackendDetailsToRecords(recordsInFile, file);
+
+ records.addAll(recordsInFile);
+ break;
+ }
+ default:
+ {
+ throw new NotImplementedException("Filesystem record format " + tableDetails.getRecordFormat() + " is not yet implemented");
+ }
+ }
+ }
+
+ return rs;
+ }
+ catch(Exception e)
+ {
+ LOG.warn("Error executing query", e);
+ throw new QException("Error executing query", e);
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** Add backend details to records about the file that they are in.
+ *******************************************************************************/
+ protected void addBackendDetailsToRecords(List recordsInFile, FILE file)
+ {
+ recordsInFile.forEach(record ->
+ {
+ record.withBackendDetail(FilesystemRecordBackendDetailFields.FULL_PATH, getFullPathForFile(file));
+ });
+ }
+
+
+
+ /*******************************************************************************
+ ** Method that subclasses can override to add pre-action things (e.g., setting up
+ ** s3 client).
+ *******************************************************************************/
+ public void preAction(QBackendMetaData backendMetaData)
+ {
+ /////////////////////////////////////////////////////////////////////
+ // noop in base class - subclasses can add functionality if needed //
+ /////////////////////////////////////////////////////////////////////
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private String customizeFileContentsAfterReading(QTableMetaData table, String fileContents) throws QException
+ {
+ Optional optionalCustomizer = table.getCustomizer("postFileRead");
+ if(optionalCustomizer.isEmpty())
+ {
+ return (fileContents);
+ }
+ QCodeReference customizer = optionalCustomizer.get();
+
+ try
+ {
+ Class> customizerClass = Class.forName(customizer.getName());
+
+ @SuppressWarnings("unchecked")
+ Function function = (Function) customizerClass.getConstructor().newInstance();
+
+ return function.apply(fileContents);
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error customizing file contents", e));
+ }
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/model/metadata/AbstractFilesystemBackendMetaData.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/model/metadata/AbstractFilesystemBackendMetaData.java
new file mode 100644
index 00000000..9002bdb8
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/model/metadata/AbstractFilesystemBackendMetaData.java
@@ -0,0 +1,80 @@
+/*
+ * 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.model.metadata;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
+
+
+/*******************************************************************************
+ ** Base class for all BackendMetaData for all filesystem-style backend modules.
+ *******************************************************************************/
+public class AbstractFilesystemBackendMetaData extends QBackendMetaData
+{
+ private String basePath;
+
+
+
+ /*******************************************************************************
+ ** Default Constructor.
+ *******************************************************************************/
+ public AbstractFilesystemBackendMetaData()
+ {
+ super();
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for basePath
+ **
+ *******************************************************************************/
+ public String getBasePath()
+ {
+ return (basePath);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for basePath
+ **
+ *******************************************************************************/
+ public void setBasePath(String basePath)
+ {
+ this.basePath = basePath;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for basePath
+ **
+ *******************************************************************************/
+ @SuppressWarnings("unchecked")
+ public T withBasePath(String basePath)
+ {
+ this.basePath = basePath;
+ return (T) this;
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/model/metadata/AbstractFilesystemTableBackendDetails.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/model/metadata/AbstractFilesystemTableBackendDetails.java
new file mode 100644
index 00000000..e98e4fad
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/model/metadata/AbstractFilesystemTableBackendDetails.java
@@ -0,0 +1,178 @@
+/*
+ * 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.model.metadata;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.QTableBackendDetails;
+
+
+/*******************************************************************************
+ ** Extension of QTableBackendDetails, with details specific to a Filesystem table.
+ *******************************************************************************/
+public class AbstractFilesystemTableBackendDetails extends QTableBackendDetails
+{
+ private String basePath;
+ private String glob;
+ private RecordFormat recordFormat;
+ private Cardinality cardinality;
+
+
+
+ /*******************************************************************************
+ ** Getter for basePath
+ **
+ *******************************************************************************/
+ public String getBasePath()
+ {
+ return basePath;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for basePath
+ **
+ *******************************************************************************/
+ public void setBasePath(String basePath)
+ {
+ this.basePath = basePath;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent Setter for basePath
+ **
+ *******************************************************************************/
+ @SuppressWarnings("unchecked")
+ public T withBasePath(String basePath)
+ {
+ this.basePath = basePath;
+ return (T) this;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for glob
+ **
+ *******************************************************************************/
+ public String getGlob()
+ {
+ return glob;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for glob
+ **
+ *******************************************************************************/
+ public void setGlob(String glob)
+ {
+ this.glob = glob;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent Setter for glob
+ **
+ *******************************************************************************/
+ @SuppressWarnings("unchecked")
+ public T withGlob(String glob)
+ {
+ this.glob = glob;
+ return (T) this;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for recordFormat
+ **
+ *******************************************************************************/
+ public RecordFormat getRecordFormat()
+ {
+ return recordFormat;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for recordFormat
+ **
+ *******************************************************************************/
+ public void setRecordFormat(RecordFormat recordFormat)
+ {
+ this.recordFormat = recordFormat;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent Setter for recordFormat
+ **
+ *******************************************************************************/
+ @SuppressWarnings("unchecked")
+ public T withRecordFormat(RecordFormat recordFormat)
+ {
+ this.recordFormat = recordFormat;
+ return ((T) this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for cardinality
+ **
+ *******************************************************************************/
+ public Cardinality getCardinality()
+ {
+ return cardinality;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for cardinality
+ **
+ *******************************************************************************/
+ public void setCardinality(Cardinality cardinality)
+ {
+ this.cardinality = cardinality;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent Setter for cardinality
+ **
+ *******************************************************************************/
+ @SuppressWarnings("unchecked")
+ public T withCardinality(Cardinality cardinality)
+ {
+ this.cardinality = cardinality;
+ return ((T) this);
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/model/metadata/Cardinality.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/model/metadata/Cardinality.java
new file mode 100644
index 00000000..0fae2ae6
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/model/metadata/Cardinality.java
@@ -0,0 +1,33 @@
+/*
+ * 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.model.metadata;
+
+
+/*******************************************************************************
+ ** Mode for filesystem backends: are all records in a single file, or are there
+ ** many files?
+ *******************************************************************************/
+public enum Cardinality
+{
+ ONE,
+ MANY
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/model/metadata/RecordFormat.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/model/metadata/RecordFormat.java
new file mode 100644
index 00000000..572876de
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/base/model/metadata/RecordFormat.java
@@ -0,0 +1,34 @@
+/*
+ * 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.model.metadata;
+
+
+/*******************************************************************************
+ ** How are records stored in the files in a filesystem backend? CSV, JSON,
+ ** future types may XML, or more exotic ones, like "BINARY" or "TEXT" (e.g., 1
+ ** record and 1 field per-file)
+ *******************************************************************************/
+public enum RecordFormat
+{
+ CSV,
+ JSON
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/exceptions/FilesystemException.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/exceptions/FilesystemException.java
new file mode 100644
index 00000000..cbda757b
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/exceptions/FilesystemException.java
@@ -0,0 +1,52 @@
+/*
+ * 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.exceptions;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+
+
+/*******************************************************************************
+ ** Checked exception to be thrown from actions within this module.
+ *******************************************************************************/
+public class FilesystemException extends QException
+{
+ /*******************************************************************************
+ ** Constructor of message
+ **
+ *******************************************************************************/
+ public FilesystemException(String message)
+ {
+ super(message);
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor of message & cause
+ **
+ *******************************************************************************/
+ public FilesystemException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/FilesystemBackendModule.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/FilesystemBackendModule.java
new file mode 100644
index 00000000..f432ec7b
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/FilesystemBackendModule.java
@@ -0,0 +1,142 @@
+/*
+ * 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;
+
+
+import java.io.File;
+import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.QTableBackendDetails;
+import com.kingsrook.qqq.backend.core.modules.interfaces.DeleteInterface;
+import com.kingsrook.qqq.backend.core.modules.interfaces.InsertInterface;
+import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface;
+import com.kingsrook.qqq.backend.core.modules.interfaces.QueryInterface;
+import com.kingsrook.qqq.backend.core.modules.interfaces.UpdateInterface;
+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.FilesystemDeleteAction;
+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.FilesystemUpdateAction;
+import com.kingsrook.qqq.backend.module.filesystem.local.model.metadata.FilesystemBackendMetaData;
+import com.kingsrook.qqq.backend.module.filesystem.local.model.metadata.FilesystemTableBackendDetails;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+
+/*******************************************************************************
+ ** QQQ Backend module for working with (local) Filesystems.
+ *******************************************************************************/
+public class FilesystemBackendModule implements QBackendModuleInterface, FilesystemBackendModuleInterface
+{
+ private static final Logger LOG = LogManager.getLogger(FilesystemBackendModule.class);
+
+
+
+ /*******************************************************************************
+ ** For filesystem backends, get the module-specific action base-class, that helps
+ ** with functions like listing and deleting files.
+ *******************************************************************************/
+ @Override
+ public AbstractBaseFilesystemAction getActionBase()
+ {
+ return (new AbstractFilesystemAction());
+ }
+
+
+
+ /*******************************************************************************
+ ** Method where a backend module must be able to provide its type (name).
+ *******************************************************************************/
+ @Override
+ public String getBackendType()
+ {
+ return ("filesystem");
+ }
+
+
+
+ /*******************************************************************************
+ ** Method to identify the class used for backend meta data for this module.
+ *******************************************************************************/
+ @Override
+ public Class extends QBackendMetaData> getBackendMetaDataClass()
+ {
+ return (FilesystemBackendMetaData.class);
+ }
+
+
+
+ /*******************************************************************************
+ ** Method to identify the class used for table-backend details for this module.
+ *******************************************************************************/
+ @Override
+ public Class extends QTableBackendDetails> getTableBackendDetailsClass()
+ {
+ return FilesystemTableBackendDetails.class;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public QueryInterface getQueryInterface()
+ {
+ return new FilesystemQueryAction();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public InsertInterface getInsertInterface()
+ {
+ return (new FilesystemInsertAction());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public UpdateInterface getUpdateInterface()
+ {
+ return (new FilesystemUpdateAction());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public DeleteInterface getDeleteInterface()
+ {
+ return (new FilesystemDeleteAction());
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/AbstractFilesystemAction.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/AbstractFilesystemAction.java
new file mode 100644
index 00000000..be5dd44b
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/AbstractFilesystemAction.java
@@ -0,0 +1,186 @@
+/*
+ * 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 java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
+import com.kingsrook.qqq.backend.module.filesystem.base.actions.AbstractBaseFilesystemAction;
+import com.kingsrook.qqq.backend.module.filesystem.exceptions.FilesystemException;
+import org.apache.commons.io.FileUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+
+/*******************************************************************************
+ ** Base class for all (local) Filesystem actions
+ *******************************************************************************/
+public class AbstractFilesystemAction extends AbstractBaseFilesystemAction
+{
+ private static final Logger LOG = LogManager.getLogger(AbstractFilesystemAction.class);
+
+
+
+ /*******************************************************************************
+ ** List the files for this table.
+ *******************************************************************************/
+ @Override
+ public List listFiles(QTableMetaData table, QBackendMetaData backendBase)
+ {
+ // todo - needs rewritten to do globbing...
+ String fullPath = getFullBasePath(table, backendBase);
+ File directory = new File(fullPath);
+ File[] files = directory.listFiles();
+
+ if(files == null)
+ {
+ return Collections.emptyList();
+ }
+
+ return (Arrays.stream(files).filter(File::isFile).toList());
+ }
+
+
+
+ /*******************************************************************************
+ ** Read the contents of a file.
+ *******************************************************************************/
+ @Override
+ public InputStream readFile(File file) throws IOException
+ {
+ return (new FileInputStream(file));
+ }
+
+
+
+ /*******************************************************************************
+ ** Write a file - to be implemented in module-specific subclasses.
+ *******************************************************************************/
+ @Override
+ public void writeFile(QBackendMetaData backend, String path, byte[] contents) throws IOException
+ {
+ FileUtils.writeByteArrayToFile(new File(path), contents);
+ }
+
+
+
+ /*******************************************************************************
+ ** Get a string that represents the full path to a file.
+ *******************************************************************************/
+ @Override
+ public String getFullPathForFile(File file)
+ {
+ return (file.getAbsolutePath());
+ }
+
+
+
+ /*******************************************************************************
+ ** In contrast with the DeleteAction, which deletes RECORDS - this is a
+ ** filesystem-(or s3, sftp, etc)-specific extension to delete an entire FILE
+ ** e.g., for post-ETL.
+ **
+ ** @throws FilesystemException if the delete is known to have failed, and the file is thought to still exit
+ *******************************************************************************/
+ @Override
+ public void deleteFile(QInstance instance, QTableMetaData table, String fileReference) throws FilesystemException
+ {
+ File file = new File(fileReference);
+ if(!file.exists())
+ {
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ // if the file doesn't exist, just exit with noop. don't throw an error - that should only //
+ // happen if the "contract" of the method is broken, and the file still exists //
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ LOG.debug("Not deleting file [{}], because it does not exist.", file);
+ return;
+ }
+
+ if(!file.delete())
+ {
+ throw (new FilesystemException("Failed to delete file: " + fileReference));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** Move a file from a source path, to a destination path.
+ **
+ ** @param destination assumed to be a file path - not a directory
+ ** @throws FilesystemException if the delete is known to have failed
+ *******************************************************************************/
+ @Override
+ public void moveFile(QInstance instance, QTableMetaData table, String source, String destination) throws FilesystemException
+ {
+ File sourceFile = new File(source);
+ File destinationFile = new File(destination);
+ File destinationParent = destinationFile.getParentFile();
+
+ if(!sourceFile.exists())
+ {
+ throw (new FilesystemException("Cannot move file " + source + ", as it does not exist."));
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // if the destination folder doesn't exist, try to make it - and fail if that fails //
+ //////////////////////////////////////////////////////////////////////////////////////
+ if(!destinationParent.exists())
+ {
+ LOG.debug("Making destination directory {} for move", destinationParent.getAbsolutePath());
+ if(!destinationParent.mkdirs())
+ {
+ throw (new FilesystemException("Failed to make destination directory " + destinationParent.getAbsolutePath() + " to move " + source + " into."));
+ }
+ }
+
+ if(!sourceFile.renameTo(destinationFile))
+ {
+ throw (new FilesystemException("Failed to move (rename) file " + source + " to " + destination));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** e.g., with a base path of /foo/
+ ** and a table path of /bar/
+ ** and a file at /foo/bar/baz.txt
+ ** give us just the baz.txt part.
+ *******************************************************************************/
+ @Override
+ public String stripBackendAndTableBasePathsFromFileName(String filePath, QBackendMetaData backend, QTableMetaData table)
+ {
+ String tablePath = getFullBasePath(table, backend);
+ String strippedPath = filePath.replaceFirst(".*" + tablePath, "");
+ return (strippedPath);
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemDeleteAction.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemDeleteAction.java
new file mode 100644
index 00000000..9843bb4e
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemDeleteAction.java
@@ -0,0 +1,60 @@
+/*
+ * 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.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteRequest;
+import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteResult;
+import com.kingsrook.qqq.backend.core.modules.interfaces.DeleteInterface;
+import org.apache.commons.lang.NotImplementedException;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class FilesystemDeleteAction implements DeleteInterface
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public DeleteResult execute(DeleteRequest deleteRequest) throws QException
+ {
+ throw new NotImplementedException("Filesystem delete not implemented");
+ /*
+ try
+ {
+ DeleteResult rs = new DeleteResult();
+ QTableMetaData table = deleteRequest.getTable();
+
+
+ // return rs;
+ }
+ catch(Exception e)
+ {
+ throw new QException("Error executing delete: " + e.getMessage(), e);
+ }
+ */
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemInsertAction.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemInsertAction.java
new file mode 100644
index 00000000..9a8ca832
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemInsertAction.java
@@ -0,0 +1,62 @@
+/*
+ * 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.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.insert.InsertRequest;
+import com.kingsrook.qqq.backend.core.model.actions.insert.InsertResult;
+import com.kingsrook.qqq.backend.core.modules.interfaces.InsertInterface;
+import org.apache.commons.lang.NotImplementedException;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class FilesystemInsertAction implements InsertInterface
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public InsertResult execute(InsertRequest insertRequest) throws QException
+ {
+ throw new NotImplementedException("Filesystem insert not implemented");
+ /*
+ try
+ {
+ InsertResult rs = new InsertResult();
+ QTableMetaData table = insertRequest.getTable();
+
+ List recordsWithStatus = new ArrayList<>();
+ rs.setRecords(recordsWithStatus);
+
+ // return rs;
+ }
+ catch(Exception e)
+ {
+ throw new QException("Error executing insert: " + e.getMessage(), e);
+ }
+ */
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemQueryAction.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemQueryAction.java
new file mode 100644
index 00000000..56dc6d5d
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemQueryAction.java
@@ -0,0 +1,45 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.module.filesystem.local.actions;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.query.QueryRequest;
+import com.kingsrook.qqq.backend.core.model.actions.query.QueryResult;
+import com.kingsrook.qqq.backend.core.modules.interfaces.QueryInterface;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class FilesystemQueryAction extends AbstractFilesystemAction implements QueryInterface
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QueryResult execute(QueryRequest queryRequest) throws QException
+ {
+ return executeQuery(queryRequest);
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemUpdateAction.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemUpdateAction.java
new file mode 100644
index 00000000..1f762164
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemUpdateAction.java
@@ -0,0 +1,63 @@
+/*
+ * 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.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest;
+import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult;
+import com.kingsrook.qqq.backend.core.modules.interfaces.UpdateInterface;
+import org.apache.commons.lang.NotImplementedException;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class FilesystemUpdateAction implements UpdateInterface
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public UpdateResult execute(UpdateRequest updateRequest) throws QException
+ {
+ throw new NotImplementedException("Filesystem update not implemented");
+ /*
+ try
+ {
+ UpdateResult rs = new UpdateResult();
+ QTableMetaData table = updateRequest.getTable();
+
+ List records = new ArrayList<>();
+ rs.setRecords(records);
+
+
+ // return rs;
+ }
+ catch(Exception e)
+ {
+ throw new QException("Error executing update: " + e.getMessage(), e);
+ }
+ */
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/model/metadata/FilesystemBackendMetaData.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/model/metadata/FilesystemBackendMetaData.java
new file mode 100644
index 00000000..02b3950e
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/model/metadata/FilesystemBackendMetaData.java
@@ -0,0 +1,45 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.module.filesystem.local.model.metadata;
+
+
+import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.AbstractFilesystemBackendMetaData;
+import com.kingsrook.qqq.backend.module.filesystem.local.FilesystemBackendModule;
+
+
+/*******************************************************************************
+ ** (local) Filesystem backend meta data.
+ *******************************************************************************/
+public class FilesystemBackendMetaData extends AbstractFilesystemBackendMetaData
+{
+
+
+ /*******************************************************************************
+ ** Default Constructor.
+ *******************************************************************************/
+ public FilesystemBackendMetaData()
+ {
+ super();
+ setBackendType(FilesystemBackendModule.class);
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/model/metadata/FilesystemTableBackendDetails.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/model/metadata/FilesystemTableBackendDetails.java
new file mode 100644
index 00000000..b2f8cee9
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/model/metadata/FilesystemTableBackendDetails.java
@@ -0,0 +1,44 @@
+/*
+ * 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.model.metadata;
+
+
+import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.AbstractFilesystemTableBackendDetails;
+import com.kingsrook.qqq.backend.module.filesystem.local.FilesystemBackendModule;
+
+
+/*******************************************************************************
+ ** (local) Filesystem specific Extension of QTableBackendDetails
+ *******************************************************************************/
+public class FilesystemTableBackendDetails extends AbstractFilesystemTableBackendDetails
+{
+
+ /*******************************************************************************
+ ** Default Constructor.
+ *******************************************************************************/
+ public FilesystemTableBackendDetails()
+ {
+ super();
+ setBackendType(FilesystemBackendModule.class);
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/etl/basic/BasicETLCleanupSourceFilesFunction.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/etl/basic/BasicETLCleanupSourceFilesFunction.java
new file mode 100644
index 00000000..f91a5b10
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/etl/basic/BasicETLCleanupSourceFilesFunction.java
@@ -0,0 +1,135 @@
+/*
+ * 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.processes.implementations.etl.basic;
+
+
+import java.io.File;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.interfaces.FunctionBody;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionRequest;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionResult;
+import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.QCodeReference;
+import com.kingsrook.qqq.backend.core.model.metadata.QCodeType;
+import com.kingsrook.qqq.backend.core.model.metadata.QCodeUsage;
+import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.QFieldType;
+import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionMetaData;
+import com.kingsrook.qqq.backend.core.modules.QBackendModuleDispatcher;
+import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface;
+import com.kingsrook.qqq.backend.core.processes.implementations.etl.basic.BasicETLProcess;
+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.actions.AbstractBaseFilesystemAction;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+
+/*******************************************************************************
+ ** Function body for performing the Cleanup step of a basic ETL process - e.g.,
+ ** after the loading, delete or move the processed file(s).
+ *******************************************************************************/
+public class BasicETLCleanupSourceFilesFunction implements FunctionBody
+{
+ private static final Logger LOG = LogManager.getLogger(BasicETLCleanupSourceFilesFunction.class);
+
+ public static final String FIELD_MOVE_OR_DELETE = "moveOrDelete";
+ public static final String FIELD_DESTINATION_FOR_MOVES = "destinationForMoves";
+
+ public static final String VALUE_MOVE = "move";
+ public static final String VALUE_DELETE = "delete";
+ public static final String FUNCTION_NAME = "cleanupSourceFiles";
+
+
+
+ /*******************************************************************************
+ ** Execute the function - using the request as input, and the result as output.
+ *******************************************************************************/
+ @Override
+ public void run(RunFunctionRequest runFunctionRequest, RunFunctionResult runFunctionResult) throws QException
+ {
+ String sourceTableName = runFunctionRequest.getValueString(BasicETLProcess.FIELD_SOURCE_TABLE);
+ QTableMetaData table = runFunctionRequest.getInstance().getTable(sourceTableName);
+ QBackendMetaData backend = runFunctionRequest.getInstance().getBackendForTable(sourceTableName);
+ QBackendModuleInterface module = new QBackendModuleDispatcher().getQBackendModule(backend);
+
+ if(!(module instanceof FilesystemBackendModuleInterface filesystemModule))
+ {
+ throw (new QException("Backend " + table.getBackendName() + " for table " + sourceTableName + " does not support this function."));
+ }
+ AbstractBaseFilesystemAction actionBase = filesystemModule.getActionBase();
+ actionBase.preAction(backend);
+
+ String sourceFilePaths = runFunctionRequest.getValueString(BasicETLCollectSourceFileNamesFunction.FIELD_SOURCE_FILE_PATHS);
+ if(!StringUtils.hasContent(sourceFilePaths))
+ {
+ LOG.info("No source file paths were specified in field [" + BasicETLCollectSourceFileNamesFunction.FIELD_SOURCE_FILE_PATHS + "]");
+ return;
+ }
+
+ String[] sourceFiles = sourceFilePaths.split(",");
+ for(String sourceFile : sourceFiles)
+ {
+ String moveOrDelete = runFunctionRequest.getValueString(FIELD_MOVE_OR_DELETE);
+ if(VALUE_DELETE.equals(moveOrDelete))
+ {
+ actionBase.deleteFile(runFunctionRequest.getInstance(), table, sourceFile);
+ }
+ else if(VALUE_MOVE.equals(moveOrDelete))
+ {
+ String destinationForMoves = runFunctionRequest.getValueString(FIELD_DESTINATION_FOR_MOVES);
+ if(!StringUtils.hasContent(destinationForMoves))
+ {
+ throw (new QException("Field [" + FIELD_DESTINATION_FOR_MOVES + "] is missing a value."));
+ }
+ String filePathWithoutBase = actionBase.stripBackendAndTableBasePathsFromFileName(sourceFile, backend, table);
+ String destinationPath = destinationForMoves + File.separator + filePathWithoutBase;
+ actionBase.moveFile(runFunctionRequest.getInstance(), table, sourceFile, destinationPath);
+ }
+ else
+ {
+ throw (new QException("Unexpected value [" + moveOrDelete + "] for field [" + FIELD_MOVE_OR_DELETE + "]. "
+ + "Must be either [" + VALUE_MOVE + "] or [" + VALUE_DELETE + "]."));
+ }
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** define the metaData that describes this function
+ *******************************************************************************/
+ public QFunctionMetaData defineFunctionMetaData()
+ {
+ return (new QFunctionMetaData()
+ .withName(FUNCTION_NAME)
+ .withCode(new QCodeReference()
+ .withName(this.getClass().getName())
+ .withCodeType(QCodeType.JAVA)
+ .withCodeUsage(QCodeUsage.FUNCTION))
+ .withInputData(new QFunctionInputMetaData()
+ .addField(new QFieldMetaData("moveOrDelete", QFieldType.STRING))
+ .addField(new QFieldMetaData("destinationForMoves", QFieldType.STRING))));
+ }
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/etl/basic/BasicETLCollectSourceFileNamesFunction.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/etl/basic/BasicETLCollectSourceFileNamesFunction.java
new file mode 100644
index 00000000..20de530f
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/etl/basic/BasicETLCollectSourceFileNamesFunction.java
@@ -0,0 +1,84 @@
+/*
+ * 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.processes.implementations.etl.basic;
+
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.interfaces.FunctionBody;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionRequest;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionResult;
+import com.kingsrook.qqq.backend.core.model.metadata.QCodeReference;
+import com.kingsrook.qqq.backend.core.model.metadata.QCodeType;
+import com.kingsrook.qqq.backend.core.model.metadata.QCodeUsage;
+import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.QFieldType;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
+import com.kingsrook.qqq.backend.core.utils.StringUtils;
+import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemRecordBackendDetailFields;
+
+
+/*******************************************************************************
+ ** Function body for collecting the file names that were discovered in the
+ ** Extract step. These will be lost during the transform, so we capture them here,
+ ** so that our Clean function can move or delete them.
+ **
+ ** TODO - need unit test!!
+ *******************************************************************************/
+public class BasicETLCollectSourceFileNamesFunction implements FunctionBody
+{
+ public static final String FUNCTION_NAME = "collectSourceFileNames";
+ public static final String FIELD_SOURCE_FILE_PATHS = "sourceFilePaths";
+
+
+
+ /*******************************************************************************
+ ** Execute the function - using the request as input, and the result as output.
+ *******************************************************************************/
+ @Override
+ public void run(RunFunctionRequest runFunctionRequest, RunFunctionResult runFunctionResult) throws QException
+ {
+ Set sourceFiles = runFunctionRequest.getRecords().stream()
+ .map(record -> record.getBackendDetailString(FilesystemRecordBackendDetailFields.FULL_PATH))
+ .collect(Collectors.toSet());
+ runFunctionResult.addValue(FIELD_SOURCE_FILE_PATHS, StringUtils.join(",", sourceFiles));
+ }
+
+
+
+ /*******************************************************************************
+ ** define the metaData that describes this function
+ *******************************************************************************/
+ public QFunctionMetaData defineFunctionMetaData()
+ {
+ return (new QFunctionMetaData()
+ .withName(FUNCTION_NAME)
+ .withCode(new QCodeReference()
+ .withName(this.getClass().getName())
+ .withCodeType(QCodeType.JAVA)
+ .withCodeUsage(QCodeUsage.FUNCTION))
+ .withOutputMetaData(new QFunctionOutputMetaData()
+ .addField(new QFieldMetaData(FIELD_SOURCE_FILE_PATHS, QFieldType.STRING))));
+ }
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/sync/FilesystemSyncFunction.java b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/sync/FilesystemSyncFunction.java
new file mode 100644
index 00000000..45264376
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/module/filesystem/processes/implementations/filesystem/sync/FilesystemSyncFunction.java
@@ -0,0 +1,138 @@
+/*
+ * 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.processes.implementations.filesystem.sync;
+
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.interfaces.FunctionBody;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionRequest;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionResult;
+import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
+import com.kingsrook.qqq.backend.core.modules.QBackendModuleDispatcher;
+import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemBackendModuleInterface;
+import com.kingsrook.qqq.backend.module.filesystem.base.actions.AbstractBaseFilesystemAction;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+
+/*******************************************************************************
+ ** Function body for collecting the file names that were discovered in the
+ ** Extract step. These will be lost during the transform, so we capture them here,
+ ** so that our Clean function can move or delete them.
+ **
+ *******************************************************************************/
+public class FilesystemSyncFunction implements FunctionBody
+{
+ private static final Logger LOG = LogManager.getLogger(FilesystemSyncFunction.class);
+
+ public static final String FUNCTION_NAME = "sync";
+
+
+
+ /*******************************************************************************
+ ** Execute the function - using the request as input, and the result as output.
+ *******************************************************************************/
+ @Override
+ public void run(RunFunctionRequest runFunctionRequest, RunFunctionResult runFunctionResult) throws QException
+ {
+ QTableMetaData sourceTable = runFunctionRequest.getInstance().getTable(runFunctionRequest.getValueString(FilesystemSyncProcess.FIELD_SOURCE_TABLE));
+ QTableMetaData archiveTable = runFunctionRequest.getInstance().getTable(runFunctionRequest.getValueString(FilesystemSyncProcess.FIELD_ARCHIVE_TABLE));
+ QTableMetaData processingTable = runFunctionRequest.getInstance().getTable(runFunctionRequest.getValueString(FilesystemSyncProcess.FIELD_PROCESSING_TABLE));
+
+ QBackendMetaData sourceBackend = runFunctionRequest.getInstance().getBackendForTable(sourceTable.getName());
+ FilesystemBackendModuleInterface sourceModule = (FilesystemBackendModuleInterface) new QBackendModuleDispatcher().getQBackendModule(sourceBackend);
+ AbstractBaseFilesystemAction sourceActionBase = sourceModule.getActionBase();
+ sourceActionBase.preAction(sourceBackend);
+ Map sourceFiles = getFileNames(sourceActionBase, sourceTable, sourceBackend);
+
+ QBackendMetaData archiveBackend = runFunctionRequest.getInstance().getBackendForTable(archiveTable.getName());
+ FilesystemBackendModuleInterface archiveModule = (FilesystemBackendModuleInterface) new QBackendModuleDispatcher().getQBackendModule(archiveBackend);
+ AbstractBaseFilesystemAction archiveActionBase = archiveModule.getActionBase();
+ archiveActionBase.preAction(archiveBackend);
+ Set archiveFiles = getFileNames(archiveActionBase, archiveTable, archiveBackend).keySet();
+
+ QBackendMetaData processingBackend = runFunctionRequest.getInstance().getBackendForTable(processingTable.getName());
+ FilesystemBackendModuleInterface processingModule = (FilesystemBackendModuleInterface) new QBackendModuleDispatcher().getQBackendModule(processingBackend);
+ AbstractBaseFilesystemAction processingActionBase = processingModule.getActionBase();
+ processingActionBase.preAction(processingBackend);
+
+ Integer maxFilesToSync = runFunctionRequest.getValueInteger(FilesystemSyncProcess.FIELD_MAX_FILES_TO_ARCHIVE);
+ int syncedFileCount = 0;
+ for(Map.Entry sourceEntry : sourceFiles.entrySet())
+ {
+ try
+ {
+ String sourceFileName = sourceEntry.getKey();
+ if(!archiveFiles.contains(sourceFileName))
+ {
+ LOG.info("Syncing file [" + sourceFileName + "] to [" + archiveTable + "] and [" + processingTable + "]");
+ InputStream inputStream = sourceActionBase.readFile(sourceEntry.getValue());
+ byte[] bytes = inputStream.readAllBytes();
+
+ String archivePath = archiveActionBase.getFullBasePath(archiveTable, archiveBackend);
+ archiveActionBase.writeFile(archiveBackend, archivePath + File.separator + sourceFileName, bytes);
+
+ String processingPath = processingActionBase.getFullBasePath(processingTable, processingBackend);
+ processingActionBase.writeFile(processingBackend, processingPath + File.separator + sourceFileName, bytes);
+ syncedFileCount++;
+
+ if(maxFilesToSync != null && syncedFileCount >= maxFilesToSync)
+ {
+ LOG.info("Breaking after syncing " + syncedFileCount + " files");
+ break;
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.error("Error processing file: " + sourceEntry, e);
+ }
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private Map getFileNames(AbstractBaseFilesystemAction actionBase, QTableMetaData table, QBackendMetaData backend)
+ {
+ List