diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/AbstractQActionBiConsumer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/AbstractQActionBiConsumer.java
new file mode 100644
index 00000000..bdd0d488
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/AbstractQActionBiConsumer.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
+import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
+
+
+/*******************************************************************************
+ ** Base class for QQQ Actions (both framework and application defined) that
+ ** have a signature like a BiConsumer - taking both Input and Output objects as
+ ** parameters, with void output.
+ *******************************************************************************/
+public abstract class AbstractQActionBiConsumer
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public abstract void execute(I input, O output) throws QException;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public Future executeAsync(I input, O output)
+ {
+ CompletableFuture completableFuture = new CompletableFuture<>();
+ Executors.newCachedThreadPool().submit(() ->
+ {
+ try
+ {
+ execute(input, output);
+ completableFuture.complete(null);
+ }
+ catch(QException e)
+ {
+ completableFuture.completeExceptionally(e);
+ }
+ });
+ return (completableFuture);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/AbstractQActionFunction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/AbstractQActionFunction.java
new file mode 100644
index 00000000..5b44f248
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/AbstractQActionFunction.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
+import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
+
+
+/*******************************************************************************
+ ** Base class for QQQ Actions (both framework and application defined) that
+ ** have a signature like a Function - taking an Input object as a parameter,
+ ** and returning an Output object.
+ *******************************************************************************/
+public abstract class AbstractQActionFunction
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public abstract O execute(I input) throws QException;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public Future executeAsync(I input)
+ {
+ CompletableFuture completableFuture = new CompletableFuture<>();
+ Executors.newCachedThreadPool().submit(() ->
+ {
+ try
+ {
+ O output = execute(input);
+ completableFuture.complete(output);
+ }
+ catch(QException e)
+ {
+ completableFuture.completeExceptionally(e);
+ }
+ });
+ return (completableFuture);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java
index 30dd9186..d28e951e 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java
@@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.actions.tables;
+import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
@@ -39,7 +40,7 @@ import org.apache.logging.log4j.Logger;
** Action to insert one or more records.
**
*******************************************************************************/
-public class InsertAction
+public class InsertAction extends AbstractQActionFunction
{
private static final Logger LOG = LogManager.getLogger(InsertAction.class);
@@ -48,6 +49,7 @@ public class InsertAction
/*******************************************************************************
**
*******************************************************************************/
+ @Override
public InsertOutput execute(InsertInput insertInput) throws QException
{
ActionHelper.validateSession(insertInput);
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java
index 7c2b5a0f..f6bab631 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java
@@ -130,6 +130,8 @@ public class QInstanceValidator
validateApps(qInstance);
validatePossibleValueSources(qInstance);
validateQueuesAndProviders(qInstance);
+
+ validateUniqueTopLevelNames(qInstance);
}
catch(Exception e)
{
@@ -146,6 +148,45 @@ public class QInstanceValidator
+ /*******************************************************************************
+ ** there can be some unexpected bad-times if you have a table and process, or
+ ** table and app (etc) with the same name (e.g., in app tree building). So,
+ ** just go ahead and make sure those are all unique.
+ *******************************************************************************/
+ private void validateUniqueTopLevelNames(QInstance qInstance)
+ {
+ String suffix = " is not unique across tables, processes, and apps (but it needs to be)";
+ Set usedNames = new HashSet<>();
+ if(qInstance.getTables() != null)
+ {
+ for(String tableName : qInstance.getTables().keySet())
+ {
+ assertCondition(!usedNames.contains(tableName), "Table name " + tableName + suffix);
+ usedNames.add(tableName);
+ }
+ }
+
+ if(qInstance.getProcesses() != null)
+ {
+ for(String processName : qInstance.getProcesses().keySet())
+ {
+ assertCondition(!usedNames.contains(processName), "Process name " + processName + suffix);
+ usedNames.add(processName);
+ }
+ }
+
+ if(qInstance.getApps() != null)
+ {
+ for(String appName : qInstance.getApps().keySet())
+ {
+ assertCondition(!usedNames.contains(appName), "App name " + appName + suffix);
+ usedNames.add(appName);
+ }
+ }
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -311,7 +352,7 @@ public class QInstanceValidator
///////////////////////////////
// validate the record label //
///////////////////////////////
- if(table.getRecordLabelFields() != null)
+ if(table.getRecordLabelFields() != null && table.getFields() != null)
{
for(String recordLabelField : table.getRecordLabelFields())
{
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java
index 8a5bb58e..308f0b7c 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java
@@ -406,6 +406,16 @@ public class QRecord implements Serializable
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public byte[] getValueByteArray(String fieldName)
+ {
+ return (ValueUtils.getValueAsByteArray(values.get(fieldName)));
+ }
+
+
+
/*******************************************************************************
** Getter for backendDetails
**
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/metadata/Auth0AuthenticationMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/metadata/Auth0AuthenticationMetaData.java
index e99d4004..274a4f3f 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/metadata/Auth0AuthenticationMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/metadata/Auth0AuthenticationMetaData.java
@@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.modules.authentication.metadata;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
@@ -32,6 +33,11 @@ public class Auth0AuthenticationMetaData extends QAuthenticationMetaData
{
private String baseUrl;
private String clientId;
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // keep this secret, on the server - don't let it be serialized and sent to a client! //
+ ////////////////////////////////////////////////////////////////////////////////////////
+ @JsonIgnore
private String clientSecret;
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java
index 022c6541..4bff494f 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java
@@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.utils;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -559,4 +560,29 @@ public class ValueUtils
throw (new QValueException("Value [" + value + "] could not be converted to a LocalTime.", e));
}
}
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static byte[] getValueAsByteArray(Serializable value)
+ {
+ if(value == null)
+ {
+ return (null);
+ }
+ else if(value instanceof byte[] ba)
+ {
+ return (ba);
+ }
+ else if(value instanceof String s)
+ {
+ return (s.getBytes(StandardCharsets.UTF_8));
+ }
+ else
+ {
+ throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to ByteArray."));
+ }
+ }
}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/QInstanceTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/QInstanceTest.java
new file mode 100644
index 00000000..5ee8d442
--- /dev/null
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/QInstanceTest.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.core.model.metadata;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+import com.kingsrook.qqq.backend.core.utils.TestUtils;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+
+/*******************************************************************************
+ ** Unit test for QInstance
+ *******************************************************************************/
+class QInstanceTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testGetTablePath() throws QException
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+
+ GetInput getInput = new GetInput(qInstance);
+ getInput.setSession(new QSession());
+
+ String tablePath = qInstance.getTablePath(getInput, TestUtils.TABLE_NAME_PERSON);
+ assertEquals("/peopleApp/person", tablePath);
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // call again (to make sure getting from memoization works - verify w/ breakpoint...) //
+ ////////////////////////////////////////////////////////////////////////////////////////
+ tablePath = qInstance.getTablePath(getInput, TestUtils.TABLE_NAME_PERSON);
+ assertEquals("/peopleApp/person", tablePath);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testGetTablePathNotInAnyApp() throws QException
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+
+ GetInput getInput = new GetInput(qInstance);
+ getInput.setSession(new QSession());
+
+ String tablePath = qInstance.getTablePath(getInput, "notATable");
+ assertNull(tablePath);
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // call again (to make sure getting from memoization works - verify w/ breakpoint...) //
+ ////////////////////////////////////////////////////////////////////////////////////////
+ tablePath = qInstance.getTablePath(getInput, "notATable");
+ assertNull(tablePath);
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/LoadViaInsertOrUpdateStepTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/LoadViaInsertOrUpdateStepTest.java
new file mode 100644
index 00000000..ba0e9d07
--- /dev/null
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/LoadViaInsertOrUpdateStepTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.processes.implementations.etl.streamedwithfrontend;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
+import com.kingsrook.qqq.backend.core.utils.TestUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+/*******************************************************************************
+ ** Unit test for com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.LoadViaInsertOrUpdateStep
+ *******************************************************************************/
+class LoadViaInsertOrUpdateStepTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @BeforeEach
+ @AfterEach
+ void beforeAndAfterEach()
+ {
+ MemoryRecordStore.getInstance().reset();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void test() throws QException
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+ List existingRecordList = List.of(
+ new QRecord().withValue("id", 47).withValue("firstName", "Tom")
+ );
+ TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY), existingRecordList);
+
+ List inputRecordList = List.of(
+ new QRecord().withValue("id", 47).withValue("firstName", "Tim"),
+ new QRecord().withValue("firstName", "John")
+ );
+ RunBackendStepInput input = new RunBackendStepInput(qInstance);
+ input.setSession(new QSession());
+ input.setRecords(inputRecordList);
+ input.addValue(LoadViaInsertOrUpdateStep.FIELD_DESTINATION_TABLE, TestUtils.TABLE_NAME_PERSON_MEMORY);
+ RunBackendStepOutput output = new RunBackendStepOutput();
+ new LoadViaInsertOrUpdateStep().run(input, output);
+
+ List qRecords = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_PERSON_MEMORY);
+ assertEquals(2, qRecords.size());
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java
index 92acf60b..fb0ed5d2 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java
@@ -129,7 +129,7 @@ public class TestUtils
public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive";
public static final String PROCESS_NAME_INCREASE_BIRTHDATE = "increaseBirthdate";
public static final String PROCESS_NAME_ADD_TO_PEOPLES_AGE = "addToPeoplesAge";
- public static final String PROCESS_NAME_BASEPULL = "basepullTest";
+ public static final String PROCESS_NAME_BASEPULL = "basepullTestProcess";
public static final String PROCESS_NAME_RUN_SHAPES_PERSON_REPORT = "runShapesPersonReport";
public static final String TABLE_NAME_PERSON_FILE = "personFile";
public static final String TABLE_NAME_PERSON_MEMORY = "personMemory";
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 3da02b62..d3fdb0a4 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
@@ -33,6 +33,8 @@ import com.kingsrook.qqq.backend.core.adapters.JsonToQRecordAdapter;
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.insert.InsertInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
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.data.QRecord;
@@ -45,6 +47,7 @@ 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.base.model.metadata.Cardinality;
import com.kingsrook.qqq.backend.module.filesystem.exceptions.FilesystemException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.NotImplementedException;
@@ -328,4 +331,40 @@ public abstract class AbstractBaseFilesystemAction
}
}
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected InsertOutput executeInsert(InsertInput insertInput) throws QException
+ {
+ try
+ {
+ InsertOutput output = new InsertOutput();
+ QTableMetaData table = insertInput.getTable();
+ QBackendMetaData backend = insertInput.getBackend();
+
+ AbstractFilesystemTableBackendDetails tableDetails = getTableBackendDetails(AbstractFilesystemTableBackendDetails.class, table);
+ if(tableDetails.getCardinality().equals(Cardinality.ONE))
+ {
+ for(QRecord record : insertInput.getRecords())
+ {
+ String fullPath = stripDuplicatedSlashes(getFullBasePath(table, backend) + File.separator + record.getValueString("fileName"));
+ writeFile(backend, fullPath, record.getValueByteArray("contents"));
+ record.addBackendDetail(FilesystemRecordBackendDetailFields.FULL_PATH, fullPath);
+ output.addRecord(record);
+ }
+ }
+ else
+ {
+ throw (new NotImplementedException("Insert is not implemented for filesystem tables with cardinality: " + tableDetails.getCardinality()));
+ }
+
+ return (output);
+ }
+ catch(Exception e)
+ {
+ throw new QException("Error executing insert: " + e.getMessage(), e);
+ }
+ }
}
diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemInsertAction.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemInsertAction.java
index 651e5c44..d222b45e 100644
--- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemInsertAction.java
+++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemInsertAction.java
@@ -26,13 +26,12 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
-import org.apache.commons.lang.NotImplementedException;
/*******************************************************************************
**
*******************************************************************************/
-public class FilesystemInsertAction implements InsertInterface
+public class FilesystemInsertAction extends AbstractFilesystemAction implements InsertInterface
{
/*******************************************************************************
@@ -40,23 +39,7 @@ public class FilesystemInsertAction implements InsertInterface
*******************************************************************************/
public InsertOutput execute(InsertInput insertInput) 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);
- }
- */
+ return (super.executeInsert(insertInput));
}
}
diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/S3InsertAction.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/S3InsertAction.java
index 2882f3ac..0c1be301 100644
--- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/S3InsertAction.java
+++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/S3InsertAction.java
@@ -26,13 +26,12 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
-import org.apache.commons.lang.NotImplementedException;
/*******************************************************************************
**
*******************************************************************************/
-public class S3InsertAction implements InsertInterface
+public class S3InsertAction extends AbstractS3Action implements InsertInterface
{
/*******************************************************************************
@@ -40,24 +39,7 @@ public class S3InsertAction implements InsertInterface
*******************************************************************************/
public InsertOutput execute(InsertInput insertInput) throws QException
{
- throw new NotImplementedException("S3 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);
- }
- */
+ return (super.executeInsert(insertInput));
}
}
diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/TestUtils.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/TestUtils.java
index 103df995..3b174399 100644
--- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/TestUtils.java
+++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/TestUtils.java
@@ -62,7 +62,9 @@ public class TestUtils
public static final String TABLE_NAME_PERSON_LOCAL_FS_JSON = "person-local-json";
public static final String TABLE_NAME_PERSON_LOCAL_FS_CSV = "person-local-csv";
+ public static final String TABLE_NAME_BLOB_LOCAL_FS = "local-blob";
public static final String TABLE_NAME_PERSON_S3 = "person-s3";
+ public static final String TABLE_NAME_BLOB_S3 = "s3-blob";
public static final String TABLE_NAME_PERSON_MOCK = "person-mock";
public static final String PROCESS_NAME_STREAMED_ETL = "etl.streamed";
@@ -132,8 +134,10 @@ public class TestUtils
qInstance.addBackend(defineLocalFilesystemBackend());
qInstance.addTable(defineLocalFilesystemJSONPersonTable());
qInstance.addTable(defineLocalFilesystemCSVPersonTable());
+ qInstance.addTable(defineLocalFilesystemBlobTable());
qInstance.addBackend(defineS3Backend());
qInstance.addTable(defineS3CSVPersonTable());
+ qInstance.addTable(defineS3BlobTable());
qInstance.addBackend(defineMockBackend());
qInstance.addTable(defineMockPersonTable());
qInstance.addProcess(defineStreamedLocalCsvToMockETLProcess());
@@ -230,6 +234,46 @@ public class TestUtils
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static QTableMetaData defineLocalFilesystemBlobTable()
+ {
+ return new QTableMetaData()
+ .withName(TABLE_NAME_BLOB_LOCAL_FS)
+ .withLabel("Blob")
+ .withBackendName(defineLocalFilesystemBackend().getName())
+ .withPrimaryKeyField("fileName")
+ .withField(new QFieldMetaData("fileName", QFieldType.STRING))
+ .withField(new QFieldMetaData("contents", QFieldType.BLOB))
+ .withBackendDetails(new FilesystemTableBackendDetails()
+ .withBasePath("blobs")
+ .withCardinality(Cardinality.ONE)
+ );
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static QTableMetaData defineS3BlobTable()
+ {
+ return new QTableMetaData()
+ .withName(TABLE_NAME_BLOB_S3)
+ .withLabel("Blob S3")
+ .withBackendName(defineS3Backend().getName())
+ .withPrimaryKeyField("fileName")
+ .withField(new QFieldMetaData("fileName", QFieldType.STRING))
+ .withField(new QFieldMetaData("contents", QFieldType.BLOB))
+ .withBackendDetails(new S3TableBackendDetails()
+ .withBasePath("blobs")
+ .withCardinality(Cardinality.ONE)
+ );
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -296,7 +340,7 @@ public class TestUtils
{
QProcessMetaData qProcessMetaData = new StreamedETLProcess().defineProcessMetaData();
qProcessMetaData.setName(PROCESS_NAME_STREAMED_ETL);
- QBackendStepMetaData backendStep = qProcessMetaData.getBackendStep(StreamedETLProcess.FUNCTION_NAME_ETL);
+ QBackendStepMetaData backendStep = qProcessMetaData.getBackendStep(StreamedETLProcess.FUNCTION_NAME_ETL);
backendStep.setCode(new QCodeReference(StreamedETLFilesystemBackendStep.class));
backendStep.getInputMetaData().getFieldThrowing(StreamedETLProcess.FIELD_SOURCE_TABLE).setDefaultValue(TABLE_NAME_PERSON_LOCAL_FS_CSV);
diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemInsertActionTest.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemInsertActionTest.java
index 92d38604..4d6ae48a 100644
--- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemInsertActionTest.java
+++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/local/actions/FilesystemInsertActionTest.java
@@ -22,11 +22,24 @@
package com.kingsrook.qqq.backend.module.filesystem.local.actions;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
+import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemRecordBackendDetailFields;
+import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.NotImplementedException;
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
@@ -39,9 +52,40 @@ public class FilesystemInsertActionTest extends FilesystemActionTest
**
*******************************************************************************/
@Test
- public void test() throws QException
+ public void testCardinalityOne() throws QException, IOException
{
- assertThrows(NotImplementedException.class, () -> new FilesystemInsertAction().execute(new InsertInput()));
+ QInstance qInstance = TestUtils.defineInstance();
+ InsertInput insertInput = new InsertInput(qInstance);
+ insertInput.setSession(new QSession());
+ insertInput.setTableName(TestUtils.TABLE_NAME_BLOB_LOCAL_FS);
+ insertInput.setRecords(List.of(
+ new QRecord().withValue("fileName", "file1.txt").withValue("contents", "Hello, World")
+ ));
+ InsertOutput insertOutput = new InsertAction().execute(insertInput);
+ assertThat(insertOutput.getRecords())
+ .allMatch(record -> record.getBackendDetailString(FilesystemRecordBackendDetailFields.FULL_PATH).contains(TestUtils.BASE_PATH))
+ .allMatch(record -> record.getBackendDetailString(FilesystemRecordBackendDetailFields.FULL_PATH).contains("blobs"));
+
+ assertEquals("Hello, World", FileUtils.readFileToString(new File(insertOutput.getRecords().get(0).getBackendDetailString(FilesystemRecordBackendDetailFields.FULL_PATH))));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void testCardinalityMany() throws QException, IOException
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+ InsertInput insertInput = new InsertInput(qInstance);
+ insertInput.setSession(new QSession());
+ insertInput.setTableName(TestUtils.TABLE_NAME_PERSON_LOCAL_FS_JSON);
+ insertInput.setRecords(List.of(
+ new QRecord().withValue("id", "1").withValue("firstName", "Bob")
+ ));
+ assertThatThrownBy(() -> new InsertAction().execute(insertInput))
+ .hasRootCauseInstanceOf(NotImplementedException.class);
}
}
\ No newline at end of file
diff --git a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/S3InsertActionTest.java b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/S3InsertActionTest.java
index 94e774f4..c69c6019 100644
--- a/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/S3InsertActionTest.java
+++ b/qqq-backend-module-filesystem/src/test/java/com/kingsrook/qqq/backend/module/filesystem/s3/actions/S3InsertActionTest.java
@@ -22,12 +22,25 @@
package com.kingsrook.qqq.backend.module.filesystem.s3.actions;
+import java.io.IOException;
+import java.util.List;
+import com.amazonaws.services.s3.model.S3Object;
+import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
+import com.kingsrook.qqq.backend.module.filesystem.base.FilesystemRecordBackendDetailFields;
import com.kingsrook.qqq.backend.module.filesystem.s3.BaseS3Test;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.NotImplementedException;
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
@@ -40,9 +53,46 @@ public class S3InsertActionTest extends BaseS3Test
**
*******************************************************************************/
@Test
- public void test() throws QException
+ public void testCardinalityOne() throws QException, IOException
{
- assertThrows(NotImplementedException.class, () -> new S3InsertAction().execute(new InsertInput()));
+ QInstance qInstance = TestUtils.defineInstance();
+
+ InsertInput insertInput = new InsertInput(qInstance);
+ insertInput.setSession(new QSession());
+ insertInput.setTableName(TestUtils.TABLE_NAME_BLOB_S3);
+ insertInput.setRecords(List.of(
+ new QRecord().withValue("fileName", "file2.txt").withValue("contents", "Hi, Bob.")
+ ));
+
+ S3InsertAction insertAction = new S3InsertAction();
+ insertAction.setS3Utils(getS3Utils());
+
+ InsertOutput insertOutput = insertAction.execute(insertInput);
+ assertThat(insertOutput.getRecords())
+ .allMatch(record -> record.getBackendDetailString(FilesystemRecordBackendDetailFields.FULL_PATH).contains("blobs"));
+
+ String fullPath = insertOutput.getRecords().get(0).getBackendDetailString(FilesystemRecordBackendDetailFields.FULL_PATH);
+ S3Object object = getAmazonS3().getObject(BUCKET_NAME, fullPath);
+ List lines = IOUtils.readLines(object.getObjectContent());
+ assertEquals("Hi, Bob.", lines.get(0));
}
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void testCardinalityMany() throws QException, IOException
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+ InsertInput insertInput = new InsertInput(qInstance);
+ insertInput.setSession(new QSession());
+ insertInput.setTableName(TestUtils.TABLE_NAME_PERSON_S3);
+ insertInput.setRecords(List.of(
+ new QRecord().withValue("id", "1").withValue("firstName", "Bob")
+ ));
+ assertThatThrownBy(() -> new InsertAction().execute(insertInput))
+ .hasRootCauseInstanceOf(NotImplementedException.class);
+ }
}
\ No newline at end of file
diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java
index 9435813d..ac4091af 100644
--- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java
+++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java
@@ -83,7 +83,7 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
rs.setRecords(outputRecords);
/////////////////////////////////////////////////////////////////////////////////////////////
- // we want to do batch updates. But, since we only update the columns columns that //
+ // we want to do batch updates. But, since we only update the columns that //
// are present in each record, it means we may have different update SQL for each //
// record. So, we will first "hash" up the records by their list of fields being updated. //
/////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/qqq-dev-tools/bin/createTableToEntityFields.groovy b/qqq-dev-tools/bin/createTableToEntityFields.groovy
index 3f3ddef1..91af3078 100755
--- a/qqq-dev-tools/bin/createTableToEntityFields.groovy
+++ b/qqq-dev-tools/bin/createTableToEntityFields.groovy
@@ -12,6 +12,7 @@ boolean writeTableMetaData = args.length > 2 ? args[2] : false;
def reader = new BufferedReader(new InputStreamReader(System.in))
String line
String allFieldNames = ""
+String dataFieldNames = ""
List fieldNameList = new ArrayList<>();
List fieldTypeList = new ArrayList<>();
@@ -64,6 +65,15 @@ while((line = reader.readLine()) != null)
""".formatted(attributes, fieldType, fieldName))
allFieldNames += '"' + fieldName + '",'
+ if(!fieldName.equals("id") && !fieldName.equals("createDate") && !fieldName.equals("modifyDate"))
+ {
+ dataFieldNames += '"' + fieldName + '",'
+ }
+}
+
+if(dataFieldNames.length() > 0)
+{
+ dataFieldNames = dataFieldNames.substring(0, dataFieldNames.length() - 1);
}
if(writeWholeClass)
@@ -162,22 +172,35 @@ if(writeTableMetaData)
.withRecordLabelFields("TODO")
.withBackendName(TODO)
.withPrimaryKeyField("id")
+ .withFieldsFromEntity({className}.class)
.withBackendDetails(new RDBMSTableBackendDetails()
.withTableName("{tableName}")
- );
+ )
+ .withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id")))
+ .withSection(new QFieldSection("data", new QIcon().withName("text_snippet"), Tier.T2, List.of({dataFieldNames})))
+ .withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
+
+ QInstanceEnricher.setInferredFieldBackendNames(qTableMetaData);
+
return (qTableMetaData);
}
"""
.replaceAll("\\{className}", className)
.replaceAll("\\{tableName}", tableName)
+ .replaceAll("\\{dataFieldNames}", dataFieldNames)
);
}
println(output);
println()
-println("All field names (e.g., for sections):")
-println(allFieldNames);
+
+if(!writeTableMetaData)
+{
+ println("All field names (e.g., for sections):")
+ allFieldNames = allFieldNames.substring(0, allFieldNames.length() - 1);
+ println(allFieldNames);
+}
println();
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java
index 4fe8bef2..b1c1ae26 100644
--- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java
@@ -123,7 +123,8 @@ public class QJavalinImplementation
private static final int SESSION_COOKIE_AGE = 60 * 60 * 24;
private static final String SESSION_ID_COOKIE_NAME = "sessionId";
- static QInstance qInstance;
+ static QInstance qInstance;
+ static QJavalinMetaData javalinMetaData = new QJavalinMetaData();
private static Supplier qInstanceHotSwapSupplier;
private static long lastQInstanceHotSwapMillis;
@@ -1063,4 +1064,25 @@ public class QJavalinImplementation
QJavalinImplementation.qInstanceHotSwapSupplier = qInstanceHotSwapSupplier;
}
+
+
+ /*******************************************************************************
+ ** Getter for javalinMetaData
+ **
+ *******************************************************************************/
+ public QJavalinMetaData getJavalinMetaData()
+ {
+ return javalinMetaData;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for javalinMetaData
+ **
+ *******************************************************************************/
+ public void setJavalinMetaData(QJavalinMetaData javalinMetaData)
+ {
+ QJavalinImplementation.javalinMetaData = javalinMetaData;
+ }
}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinMetaData.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinMetaData.java
new file mode 100644
index 00000000..f383e236
--- /dev/null
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinMetaData.java
@@ -0,0 +1,66 @@
+/*
+ * 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.javalin;
+
+
+/*******************************************************************************
+ ** MetaData specific to a QQQ Javalin server.
+ *******************************************************************************/
+public class QJavalinMetaData
+{
+ private String uploadedFileArchiveTableName;
+
+
+
+ /*******************************************************************************
+ ** Getter for uploadedFileArchiveTableName
+ **
+ *******************************************************************************/
+ public String getUploadedFileArchiveTableName()
+ {
+ return uploadedFileArchiveTableName;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for uploadedFileArchiveTableName
+ **
+ *******************************************************************************/
+ public void setUploadedFileArchiveTableName(String uploadedFileArchiveTableName)
+ {
+ this.uploadedFileArchiveTableName = uploadedFileArchiveTableName;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for uploadedFileArchiveTableName
+ **
+ *******************************************************************************/
+ public QJavalinMetaData withUploadedFileArchiveTableName(String uploadedFileArchiveTableName)
+ {
+ this.uploadedFileArchiveTableName = uploadedFileArchiveTableName;
+ return (this);
+ }
+
+}
diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java
index b79cd703..cbdae90b 100644
--- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java
+++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java
@@ -22,10 +22,12 @@
package com.kingsrook.qqq.backend.javalin;
+import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
+import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -42,6 +44,8 @@ import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
import com.kingsrook.qqq.backend.core.actions.async.JobGoingAsyncException;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
+import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
+import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
@@ -49,6 +53,7 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
import com.kingsrook.qqq.backend.core.model.actions.processes.QUploadedFile;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
@@ -290,6 +295,8 @@ public class QJavalinProcessHandler
TempFileStateProvider.getInstance().put(key, qUploadedFile);
LOG.info("Stored uploaded file in TempFileStateProvider under key: " + key);
runProcessInput.addValue(QUploadedFile.DEFAULT_UPLOADED_FILE_FIELD_NAME, key);
+
+ archiveUploadedFile(runProcessInput, qUploadedFile);
}
/////////////////////////////////////////////////////////////
@@ -319,6 +326,28 @@ public class QJavalinProcessHandler
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static void archiveUploadedFile(RunProcessInput runProcessInput, QUploadedFile qUploadedFile)
+ {
+ String fileName = new QValueFormatter().formatDate(LocalDate.now())
+ + File.separator + runProcessInput.getProcessName()
+ + File.separator + qUploadedFile.getFilename();
+
+ InsertInput insertInput = new InsertInput(QJavalinImplementation.qInstance);
+ insertInput.setSession(runProcessInput.getSession());
+ insertInput.setTableName(QJavalinImplementation.javalinMetaData.getUploadedFileArchiveTableName());
+ insertInput.setRecords(List.of(new QRecord()
+ .withValue("fileName", fileName)
+ .withValue("contents", qUploadedFile.getBytes())
+ ));
+
+ new InsertAction().executeAsync(insertInput);
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/