diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractHTMLWidgetRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractHTMLWidgetRenderer.java
index 1efa034e..3462bf89 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractHTMLWidgetRenderer.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractHTMLWidgetRenderer.java
@@ -22,11 +22,14 @@
package com.kingsrook.qqq.backend.core.actions.dashboard;
+import java.io.Serializable;
import java.net.URLEncoder;
import java.nio.charset.Charset;
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.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
@@ -37,6 +40,7 @@ import com.kingsrook.qqq.backend.core.utils.JsonUtils;
public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
{
+
/*******************************************************************************
**
*******************************************************************************/
@@ -104,7 +108,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
/*******************************************************************************
**
*******************************************************************************/
- protected String linkTableBulkLoad(RenderWidgetInput input, String tableName) throws QException
+ public static String linkTableBulkLoad(RenderWidgetInput input, String tableName) throws QException
{
String tablePath = input.getInstance().getTablePath(input, tableName);
return (tablePath + "/" + tableName + ".bulkInsert");
@@ -115,10 +119,34 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
/*******************************************************************************
**
*******************************************************************************/
- protected String linkTableFilter(RenderWidgetInput input, String tableName, QQueryFilter filter) throws QException
+ public static String linkTableFilter(RenderWidgetInput input, String tableName, QQueryFilter filter) throws QException
{
String tablePath = input.getInstance().getTablePath(input, tableName);
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
}
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static String linkRecordEdit(AbstractActionInput input, String tableName, Serializable recordId) throws QException
+ {
+ String tablePath = input.getInstance().getTablePath(input, tableName);
+ return (tablePath + "/" + recordId + "/edit");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static String linkProcessForRecord(AbstractActionInput input, String processName, Serializable recordId) throws QException
+ {
+ QProcessMetaData process = input.getInstance().getProcess(processName);
+ String tableName = process.getTableName();
+ String tablePath = input.getInstance().getTablePath(input, tableName);
+ return (tablePath + "/" + recordId + "/" + processName);
+ }
+
}
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 d28e951e..cbca6067 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
@@ -27,6 +27,7 @@ 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;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
+import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
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;
@@ -55,6 +56,9 @@ public class InsertAction extends AbstractQActionFunction.
+ */
+
+package com.kingsrook.qqq.backend.core.actions.values;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+import com.kingsrook.qqq.backend.core.utils.StringUtils;
+
+
+/*******************************************************************************
+ ** Utility class to apply value behaviors to records.
+ *******************************************************************************/
+public class ValueBehaviorApplier
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static void applyFieldBehaviors(QInstance instance, QTableMetaData table, List recordList)
+ {
+ for(QFieldMetaData field : table.getFields().values())
+ {
+ String fieldName = field.getName();
+ if(field.getType().equals(QFieldType.STRING) && field.getMaxLength() != null)
+ {
+ applyValueTooLongBehavior(instance, recordList, field, fieldName);
+ }
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static void applyValueTooLongBehavior(QInstance instance, List recordList, QFieldMetaData field, String fieldName)
+ {
+ ValueTooLongBehavior valueTooLongBehavior = field.getBehavior(instance, ValueTooLongBehavior.class);
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
+ // don't process PASS_THROUGH - so we don't have to iterate over the whole record list to do noop //
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
+ if(valueTooLongBehavior != null && !valueTooLongBehavior.equals(ValueTooLongBehavior.PASS_THROUGH))
+ {
+ for(QRecord record : recordList)
+ {
+ String value = record.getValueString(fieldName);
+ if(value != null && value.length() > field.getMaxLength())
+ {
+ switch(valueTooLongBehavior)
+ {
+ case TRUNCATE -> record.setValue(fieldName, StringUtils.safeTruncate(value, field.getMaxLength()));
+ case TRUNCATE_ELLIPSIS -> record.setValue(fieldName, StringUtils.safeTruncate(value, field.getMaxLength(), "..."));
+ case ERROR -> record.addError("The value for " + field.getLabel() + " is too long (max allowed length=" + field.getMaxLength() + ")");
+ case PASS_THROUGH ->
+ {
+ }
+ default -> throw new IllegalStateException("Unexpected valueTooLongBehavior: " + valueTooLongBehavior);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QField.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QField.java
index 1988bd6a..3cdec2a9 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QField.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/data/QField.java
@@ -26,6 +26,7 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
/*******************************************************************************
@@ -67,6 +68,16 @@ public @interface QField
*******************************************************************************/
String possibleValueSourceName() default "";
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ int maxLength() default Integer.MAX_VALUE;
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ ValueTooLongBehavior valueTooLongBehavior() default ValueTooLongBehavior.PASS_THROUGH;
+
//////////////////////////////////////////////////////////////////////////////////////////
// new attributes here likely need implementation in QFieldMetaData.constructFromGetter //
//////////////////////////////////////////////////////////////////////////////////////////
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/FieldBehavior.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/FieldBehavior.java
new file mode 100644
index 00000000..db4a2b86
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/FieldBehavior.java
@@ -0,0 +1,31 @@
+/*
+ * 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.fields;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface FieldBehavior
+{
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java
index 9000614f..ca11da98 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java
@@ -25,12 +25,16 @@ package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import com.github.hervian.reflection.Fun;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@@ -56,6 +60,19 @@ public class QFieldMetaData implements Cloneable
private Serializable defaultValue;
private String possibleValueSourceName;
+ private Integer maxLength;
+ private Set behaviors;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // w/ longer-term vision: //
+ // - more enums that implement ValueTooLongBehavior e.g., NumberOutsideRangeBehavior or DecimalPrecisionErrorBehavior //
+ // - QInstance.Set defaultFieldBehaviors //
+ // - QBackendMetaData.Set defaultFieldBehaviors //
+ // - QTableMetaData.Set defaultFieldBehaviors //
+ // - inherit behaviors all the way down (up?) //
+ // - instance validation to make sure you don’t specify more than 1 behavior of a given type at a given level. //
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
private List adornments;
@@ -176,6 +193,16 @@ public class QFieldMetaData implements Cloneable
{
setPossibleValueSourceName(fieldAnnotation.possibleValueSourceName());
}
+
+ if(fieldAnnotation.maxLength() != Integer.MAX_VALUE)
+ {
+ setMaxLength(fieldAnnotation.maxLength());
+ }
+
+ if(fieldAnnotation.valueTooLongBehavior() != ValueTooLongBehavior.PASS_THROUGH)
+ {
+ withBehavior(fieldAnnotation.valueTooLongBehavior());
+ }
}
}
catch(QException qe)
@@ -532,4 +559,118 @@ public class QFieldMetaData implements Cloneable
return (this);
}
+
+
+ /*******************************************************************************
+ ** Getter for maxLength
+ **
+ *******************************************************************************/
+ public Integer getMaxLength()
+ {
+ return maxLength;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for maxLength
+ **
+ *******************************************************************************/
+ public void setMaxLength(Integer maxLength)
+ {
+ this.maxLength = maxLength;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for maxLength
+ **
+ *******************************************************************************/
+ public QFieldMetaData withMaxLength(Integer maxLength)
+ {
+ this.maxLength = maxLength;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for behaviors
+ **
+ *******************************************************************************/
+ public Set getBehaviors()
+ {
+ return behaviors;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public T getBehavior(QInstance instance, Class behaviorType)
+ {
+ for(FieldBehavior fieldBehavior : CollectionUtils.nonNullCollection(behaviors))
+ {
+ if(behaviorType.isInstance(fieldBehavior))
+ {
+ return (behaviorType.cast(fieldBehavior));
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+ // todo - cascade/inherit behaviors down from table, backend, instance //
+ /////////////////////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////
+ // return default behavior for this type //
+ ///////////////////////////////////////////
+ if(behaviorType.equals(ValueTooLongBehavior.class))
+ {
+ return behaviorType.cast(ValueTooLongBehavior.getDefault());
+ }
+
+ return (null);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for behaviors
+ **
+ *******************************************************************************/
+ public void setBehaviors(Set behaviors)
+ {
+ this.behaviors = behaviors;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for behaviors
+ **
+ *******************************************************************************/
+ public QFieldMetaData withBehaviors(Set behaviors)
+ {
+ this.behaviors = behaviors;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for behaviors
+ **
+ *******************************************************************************/
+ public QFieldMetaData withBehavior(FieldBehavior behavior)
+ {
+ if(behaviors == null)
+ {
+ behaviors = new HashSet<>();
+ }
+ this.behaviors.add(behavior);
+ return (this);
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/ValueTooLongBehavior.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/ValueTooLongBehavior.java
new file mode 100644
index 00000000..57b465be
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/ValueTooLongBehavior.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.core.model.metadata.fields;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public enum ValueTooLongBehavior implements FieldBehavior
+{
+ TRUNCATE,
+ TRUNCATE_ELLIPSIS,
+ ERROR,
+ PASS_THROUGH;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static FieldBehavior getDefault()
+ {
+ return PASS_THROUGH;
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java
index 18fb4a73..1ec145b1 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java
@@ -28,12 +28,15 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
+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.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
@@ -45,6 +48,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
+import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
@@ -304,6 +308,41 @@ public class GeneralProcessUtils
+ /*******************************************************************************
+ ** Load all rows from a table, into a map, keyed by the keyFieldName - typed as
+ ** the specified keyType.
+ **
+ ** Note - null values from the key field are NOT put in the map.
+ **
+ ** If multiple values are found for the key, they'll squash each other, and only
+ ** one random value will appear.
+ **
+ ** Also, note, this is inherently unsafe, if you were to call it on a table with
+ ** too many rows... Caveat emptor.
+ *******************************************************************************/
+ public static Map loadTableToMap(AbstractActionInput parentActionInput, String tableName, Class keyType, String keyFieldName) throws QException
+ {
+ QueryInput queryInput = new QueryInput(parentActionInput.getInstance());
+ queryInput.setSession(parentActionInput.getSession());
+ queryInput.setTableName(tableName);
+ QueryOutput queryOutput = new QueryAction().execute(queryInput);
+ List records = queryOutput.getRecords();
+
+ Map map = new HashMap<>();
+ for(QRecord record : records)
+ {
+ Serializable value = record.getValue(keyFieldName);
+ if(value != null)
+ {
+ T valueAsT = ValueUtils.getValueAsType(keyType, value);
+ map.put(valueAsT, record);
+ }
+ }
+ return (map);
+ }
+
+
+
/*******************************************************************************
** Note - null values from the key field are NOT put in the map.
*******************************************************************************/
@@ -406,4 +445,19 @@ public class GeneralProcessUtils
return (rs);
}
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static Integer count(AbstractActionInput input, String tableName, QQueryFilter filter) throws QException
+ {
+ CountInput countInput = new CountInput(input.getInstance());
+ countInput.setSession(input.getSession());
+ countInput.setTableName(tableName);
+ countInput.setFilter(filter);
+ CountOutput countOutput = new CountAction().execute(countInput);
+ return (countOutput.getCount());
+
+ }
}
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 4bff494f..52fb127c 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
@@ -585,4 +585,51 @@ public class ValueUtils
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to ByteArray."));
}
}
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @SuppressWarnings("unchecked")
+ public static T getValueAsType(Class type, Serializable value)
+ {
+ if(type.equals(Integer.class))
+ {
+ return (T) getValueAsInteger(value);
+ }
+ else if(type.equals(String.class))
+ {
+ return (T) getValueAsString(value);
+ }
+ else if(type.equals(Boolean.class))
+ {
+ return (T) getValueAsBoolean(value);
+ }
+ else if(type.equals(BigDecimal.class))
+ {
+ return (T) getValueAsBigDecimal(value);
+ }
+ else if(type.equals(LocalDateTime.class))
+ {
+ return (T) getValueAsLocalDateTime(value);
+ }
+ else if(type.equals(LocalDate.class))
+ {
+ return (T) getValueAsLocalDate(value);
+ }
+ else if(type.equals(Instant.class))
+ {
+ return (T) getValueAsInstant(value);
+ }
+ else if(type.equals(byte[].class))
+ {
+ return (T) getValueAsByteArray(value);
+ }
+ else
+ {
+ throw new QValueException("Unsupported type [" + type.getSimpleName() + "] in getValueAsType.");
+ }
+ }
+
}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/values/ValueBehaviorApplierTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/values/ValueBehaviorApplierTest.java
new file mode 100644
index 00000000..83841c18
--- /dev/null
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/values/ValueBehaviorApplierTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.values;
+
+
+import java.util.List;
+import java.util.Optional;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+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.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+
+/*******************************************************************************
+ ** Unit test for ValueBehaviorApplier
+ *******************************************************************************/
+class ValueBehaviorApplierTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testValueTooLongNormalCases()
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+ QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
+ table.getField("firstName").withMaxLength(10).withBehavior(ValueTooLongBehavior.TRUNCATE);
+ table.getField("lastName").withMaxLength(10).withBehavior(ValueTooLongBehavior.TRUNCATE_ELLIPSIS);
+ table.getField("email").withMaxLength(20).withBehavior(ValueTooLongBehavior.ERROR);
+
+ List recordList = List.of(
+ new QRecord().withValue("id", 1).withValue("firstName", "First name too long").withValue("lastName", "Smith").withValue("email", "john@smith.com"),
+ new QRecord().withValue("id", 2).withValue("firstName", "John").withValue("lastName", "Last name too long").withValue("email", "john@smith.com"),
+ new QRecord().withValue("id", 3).withValue("firstName", "First name too long").withValue("lastName", "Smith").withValue("email", "john.smith@emaildomainwayytolongtofit.com")
+ );
+ ValueBehaviorApplier.applyFieldBehaviors(qInstance, table, recordList);
+
+ assertEquals("First name", getRecordById(recordList, 1).getValueString("firstName"));
+ assertEquals("Last na...", getRecordById(recordList, 2).getValueString("lastName"));
+ assertEquals("john.smith@emaildomainwayytolongtofit.com", getRecordById(recordList, 3).getValueString("email"));
+ assertFalse(getRecordById(recordList, 3).getErrors().isEmpty());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testValueTooLongEdgeCases()
+ {
+ QInstance qInstance = TestUtils.defineInstance();
+ QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // make sure PASS THROUGH actually does nothing, and that a maxLength w/ no behavior specified also does nothing (e.g., does PASS_THROUGH) //
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ table.getField("firstName").withMaxLength(10).withBehavior(ValueTooLongBehavior.PASS_THROUGH);
+ table.getField("lastName").withMaxLength(10);
+
+ List recordList = List.of(
+ ////////////////////////////////////////////////////////////////
+ // make sure nulls and empty are okay, and don't get changed. //
+ ////////////////////////////////////////////////////////////////
+ new QRecord().withValue("id", 1).withValue("firstName", "First name too long").withValue("lastName", null).withValue("email", "john@smith.com"),
+ new QRecord().withValue("id", 2).withValue("firstName", "").withValue("lastName", "Last name too long").withValue("email", "john@smith.com")
+ );
+ ValueBehaviorApplier.applyFieldBehaviors(qInstance, table, recordList);
+
+ assertEquals("First name too long", getRecordById(recordList, 1).getValueString("firstName"));
+ assertNull(getRecordById(recordList, 1).getValueString("lastName"));
+ assertEquals("Last name too long", getRecordById(recordList, 2).getValueString("lastName"));
+ assertEquals("", getRecordById(recordList, 2).getValueString("firstName"));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static QRecord getRecordById(List recordList, Integer id)
+ {
+ Optional recordOpt = recordList.stream().filter(r -> r.getValueInteger("id").equals(id)).findFirst();
+ if(recordOpt.isEmpty())
+ {
+ fail("Didn't find record with id=" + id);
+ }
+ return (recordOpt.get());
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java
index 998fae6d..ae00eccf 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java
@@ -22,8 +22,10 @@
package com.kingsrook.qqq.backend.core.utils;
+import java.io.Serializable;
import java.math.BigDecimal;
import java.math.MathContext;
+import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -34,6 +36,7 @@ import java.util.GregorianCalendar;
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -231,6 +234,7 @@ class ValueUtilsTest
}
+
/*******************************************************************************
**
*******************************************************************************/
@@ -251,4 +255,20 @@ class ValueUtilsTest
assertThat(assertThrows(QValueException.class, () -> ValueUtils.getValueAsInstant(new Object())).getMessage()).contains("Unsupported class");
}
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testGetValueAsType()
+ {
+ assertEquals(1, ValueUtils.getValueAsType(Integer.class, "1"));
+ assertEquals("1", ValueUtils.getValueAsType(String.class, 1));
+ assertEquals(BigDecimal.ONE, ValueUtils.getValueAsType(BigDecimal.class, 1));
+ assertEquals(true, ValueUtils.getValueAsType(Boolean.class, "true"));
+ assertArrayEquals("a".getBytes(StandardCharsets.UTF_8), ValueUtils.getValueAsType(byte[].class, "a"));
+ assertThrows(QValueException.class, () -> ValueUtils.getValueAsType(Serializable.class, 1));
+ }
+
}
\ No newline at end of file
diff --git a/qqq-middleware-javalin/pom.xml b/qqq-middleware-javalin/pom.xml
index efef7d1e..d225ee0f 100644
--- a/qqq-middleware-javalin/pom.xml
+++ b/qqq-middleware-javalin/pom.xml
@@ -60,18 +60,18 @@
io.javalin
javalin
- 4.6.1
+ 5.1.4
com.konghq
unirest-java
- 3.4.00
+ 3.13.12
test
com.h2database
h2
- 2.1.210
+ 2.1.214
test
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 cbdae90b..9f2c9a7f 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
@@ -26,6 +26,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.ArrayList;
@@ -287,16 +288,19 @@ public class QJavalinProcessHandler
////////////////////////////
for(UploadedFile uploadedFile : context.uploadedFiles())
{
- QUploadedFile qUploadedFile = new QUploadedFile();
- qUploadedFile.setBytes(uploadedFile.getContent().readAllBytes());
- qUploadedFile.setFilename(uploadedFile.getFilename());
+ try(InputStream content = uploadedFile.content())
+ {
+ QUploadedFile qUploadedFile = new QUploadedFile();
+ qUploadedFile.setBytes(content.readAllBytes());
+ qUploadedFile.setFilename(uploadedFile.filename());
- UUIDAndTypeStateKey key = new UUIDAndTypeStateKey(StateType.UPLOADED_FILE);
- TempFileStateProvider.getInstance().put(key, qUploadedFile);
- LOG.info("Stored uploaded file in TempFileStateProvider under key: " + key);
- runProcessInput.addValue(QUploadedFile.DEFAULT_UPLOADED_FILE_FIELD_NAME, key);
+ UUIDAndTypeStateKey key = new UUIDAndTypeStateKey(StateType.UPLOADED_FILE);
+ 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);
+ archiveUploadedFile(runProcessInput, qUploadedFile);
+ }
}
/////////////////////////////////////////////////////////////
diff --git a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleJavalinServer.java b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleJavalinServer.java
index e88ec6c5..93f987e7 100644
--- a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleJavalinServer.java
+++ b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/SampleJavalinServer.java
@@ -25,6 +25,7 @@ package com.kingsrook.sampleapp;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
import io.javalin.Javalin;
+import io.javalin.plugin.bundled.CorsPluginConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -66,8 +67,8 @@ public class SampleJavalinServer
QJavalinImplementation qJavalinImplementation = new QJavalinImplementation(qInstance);
javalinService = Javalin.create(config ->
{
- // todo - not all!!
- config.enableCorsForAllOrigins();
+ // todo - not all?
+ config.plugins.enableCors(cors -> cors.add(CorsPluginConfig::anyHost));
}).start(PORT);
javalinService.routes(qJavalinImplementation.getRoutes());
@@ -88,8 +89,7 @@ public class SampleJavalinServer
});
javalinService.before(QJavalinImplementation::hotSwapQInstance);
- javalinService.after(ctx ->
- ctx.res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"));
+ javalinService.after(ctx -> ctx.res().setHeader("Access-Control-Allow-Origin", "http://localhost:3000"));
}
catch(Exception e)
{