Adding maxLength to fields, along with initial version of FieldBehviors and ValueBehaviorApplier, including ValueTooLongBehavior

This commit is contained in:
2022-11-22 12:23:58 -06:00
parent 209ada8065
commit aef8f5cd59
15 changed files with 612 additions and 17 deletions

View File

@ -22,11 +22,14 @@
package com.kingsrook.qqq.backend.core.actions.dashboard; package com.kingsrook.qqq.backend.core.actions.dashboard;
import java.io.Serializable;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput; 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; 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 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); String tablePath = input.getInstance().getTablePath(input, tableName);
return (tablePath + "/" + tableName + ".bulkInsert"); 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); String tablePath = input.getInstance().getTablePath(input, tableName);
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset())); 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);
}
} }

View File

@ -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.QBackendTransaction;
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus; 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.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.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; 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.insert.InsertOutput;
@ -55,6 +56,9 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
ActionHelper.validateSession(insertInput); ActionHelper.validateSession(insertInput);
setAutomationStatusField(insertInput); setAutomationStatusField(insertInput);
ValueBehaviorApplier.applyFieldBehaviors(insertInput.getInstance(), insertInput.getTable(), insertInput.getRecords());
// todo - need to handle records with errors coming out of here...
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput); QBackendModuleInterface qModule = getBackendModuleInterface(insertInput);
// todo pre-customization - just get to modify the request? // todo pre-customization - just get to modify the request?
InsertOutput insertOutput = qModule.getInsertInterface().execute(insertInput); InsertOutput insertOutput = qModule.getInsertInterface().execute(insertInput);

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.tables;
import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus; 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.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.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
@ -46,6 +47,9 @@ public class UpdateAction
ActionHelper.validateSession(updateInput); ActionHelper.validateSession(updateInput);
setAutomationStatusField(updateInput); setAutomationStatusField(updateInput);
ValueBehaviorApplier.applyFieldBehaviors(updateInput.getInstance(), updateInput.getTable(), updateInput.getRecords());
// todo - need to handle records with errors coming out of here...
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher(); QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(updateInput.getBackend()); QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(updateInput.getBackend());
// todo pre-customization - just get to modify the request? // todo pre-customization - just get to modify the request?

View File

@ -0,0 +1,90 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.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<QRecord> 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<QRecord> 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);
}
}
}
}
}
}

View File

@ -26,6 +26,7 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; 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 ""; String possibleValueSourceName() default "";
/*******************************************************************************
**
*******************************************************************************/
int maxLength() default Integer.MAX_VALUE;
/*******************************************************************************
**
*******************************************************************************/
ValueTooLongBehavior valueTooLongBehavior() default ValueTooLongBehavior.PASS_THROUGH;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// new attributes here likely need implementation in QFieldMetaData.constructFromGetter // // new attributes here likely need implementation in QFieldMetaData.constructFromGetter //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.fields;
/*******************************************************************************
**
*******************************************************************************/
public interface FieldBehavior
{
}

View File

@ -25,12 +25,16 @@ package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import com.github.hervian.reflection.Fun; import com.github.hervian.reflection.Fun;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; 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; import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -56,6 +60,19 @@ public class QFieldMetaData implements Cloneable
private Serializable defaultValue; private Serializable defaultValue;
private String possibleValueSourceName; private String possibleValueSourceName;
private Integer maxLength;
private Set<FieldBehavior> behaviors;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// w/ longer-term vision: //
// - more enums that implement ValueTooLongBehavior e.g., NumberOutsideRangeBehavior or DecimalPrecisionErrorBehavior //
// - QInstance.Set<FieldBehavior> defaultFieldBehaviors //
// - QBackendMetaData.Set<FieldBehavior> defaultFieldBehaviors //
// - QTableMetaData.Set<FieldBehavior> defaultFieldBehaviors //
// - inherit behaviors all the way down (up?) //
// - instance validation to make sure you dont specify more than 1 behavior of a given type at a given level. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private List<FieldAdornment> adornments; private List<FieldAdornment> adornments;
@ -176,6 +193,16 @@ public class QFieldMetaData implements Cloneable
{ {
setPossibleValueSourceName(fieldAnnotation.possibleValueSourceName()); setPossibleValueSourceName(fieldAnnotation.possibleValueSourceName());
} }
if(fieldAnnotation.maxLength() != Integer.MAX_VALUE)
{
setMaxLength(fieldAnnotation.maxLength());
}
if(fieldAnnotation.valueTooLongBehavior() != ValueTooLongBehavior.PASS_THROUGH)
{
withBehavior(fieldAnnotation.valueTooLongBehavior());
}
} }
} }
catch(QException qe) catch(QException qe)
@ -532,4 +559,118 @@ public class QFieldMetaData implements Cloneable
return (this); 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<FieldBehavior> getBehaviors()
{
return behaviors;
}
/*******************************************************************************
**
*******************************************************************************/
public <T extends FieldBehavior> T getBehavior(QInstance instance, Class<T> 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<FieldBehavior> behaviors)
{
this.behaviors = behaviors;
}
/*******************************************************************************
** Fluent setter for behaviors
**
*******************************************************************************/
public QFieldMetaData withBehaviors(Set<FieldBehavior> 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);
}
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -28,12 +28,15 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; 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.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; 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.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; 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.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; 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.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.utils.ListingHash; import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.StringUtils; 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 <T extends Serializable> Map<T, QRecord> loadTableToMap(AbstractActionInput parentActionInput, String tableName, Class<T> 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<QRecord> records = queryOutput.getRecords();
Map<T, QRecord> 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. ** Note - null values from the key field are NOT put in the map.
*******************************************************************************/ *******************************************************************************/
@ -406,4 +445,19 @@ public class GeneralProcessUtils
return (rs); 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());
}
} }

View File

@ -585,4 +585,51 @@ public class ValueUtils
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to ByteArray.")); throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to ByteArray."));
} }
} }
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("unchecked")
public static <T extends Serializable> T getValueAsType(Class<T> 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.");
}
}
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<QRecord> 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<QRecord> 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<QRecord> recordList, Integer id)
{
Optional<QRecord> 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());
}
}

View File

@ -22,8 +22,10 @@
package com.kingsrook.qqq.backend.core.utils; package com.kingsrook.qqq.backend.core.utils;
import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.nio.charset.StandardCharsets;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -34,6 +36,7 @@ import java.util.GregorianCalendar;
import com.kingsrook.qqq.backend.core.exceptions.QValueException; import com.kingsrook.qqq.backend.core.exceptions.QValueException;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; 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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull; 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"); 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));
}
} }

View File

@ -60,18 +60,18 @@
<dependency> <dependency>
<groupId>io.javalin</groupId> <groupId>io.javalin</groupId>
<artifactId>javalin</artifactId> <artifactId>javalin</artifactId>
<version>4.6.1</version> <version>5.1.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.konghq</groupId> <groupId>com.konghq</groupId>
<artifactId>unirest-java</artifactId> <artifactId>unirest-java</artifactId>
<version>3.4.00</version> <version>3.13.12</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
<version>2.1.210</version> <version>2.1.214</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -26,6 +26,7 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
@ -286,10 +287,12 @@ public class QJavalinProcessHandler
// process uploaded files // // process uploaded files //
//////////////////////////// ////////////////////////////
for(UploadedFile uploadedFile : context.uploadedFiles()) for(UploadedFile uploadedFile : context.uploadedFiles())
{
try(InputStream content = uploadedFile.content())
{ {
QUploadedFile qUploadedFile = new QUploadedFile(); QUploadedFile qUploadedFile = new QUploadedFile();
qUploadedFile.setBytes(uploadedFile.getContent().readAllBytes()); qUploadedFile.setBytes(content.readAllBytes());
qUploadedFile.setFilename(uploadedFile.getFilename()); qUploadedFile.setFilename(uploadedFile.filename());
UUIDAndTypeStateKey key = new UUIDAndTypeStateKey(StateType.UPLOADED_FILE); UUIDAndTypeStateKey key = new UUIDAndTypeStateKey(StateType.UPLOADED_FILE);
TempFileStateProvider.getInstance().put(key, qUploadedFile); TempFileStateProvider.getInstance().put(key, qUploadedFile);
@ -298,6 +301,7 @@ public class QJavalinProcessHandler
archiveUploadedFile(runProcessInput, qUploadedFile); archiveUploadedFile(runProcessInput, qUploadedFile);
} }
}
///////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////
// deal with params that specify an initial-records filter // // deal with params that specify an initial-records filter //

View File

@ -25,6 +25,7 @@ package com.kingsrook.sampleapp;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation; import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.plugin.bundled.CorsPluginConfig;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -66,8 +67,8 @@ public class SampleJavalinServer
QJavalinImplementation qJavalinImplementation = new QJavalinImplementation(qInstance); QJavalinImplementation qJavalinImplementation = new QJavalinImplementation(qInstance);
javalinService = Javalin.create(config -> javalinService = Javalin.create(config ->
{ {
// todo - not all!! // todo - not all?
config.enableCorsForAllOrigins(); config.plugins.enableCors(cors -> cors.add(CorsPluginConfig::anyHost));
}).start(PORT); }).start(PORT);
javalinService.routes(qJavalinImplementation.getRoutes()); javalinService.routes(qJavalinImplementation.getRoutes());
@ -88,8 +89,7 @@ public class SampleJavalinServer
}); });
javalinService.before(QJavalinImplementation::hotSwapQInstance); javalinService.before(QJavalinImplementation::hotSwapQInstance);
javalinService.after(ctx -> javalinService.after(ctx -> ctx.res().setHeader("Access-Control-Allow-Origin", "http://localhost:3000"));
ctx.res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"));
} }
catch(Exception e) catch(Exception e)
{ {