diff --git a/announcements/src/org/labkey/announcements/AnnouncementsController.java b/announcements/src/org/labkey/announcements/AnnouncementsController.java
index 42637b9a1f2..40dada4a7b5 100644
--- a/announcements/src/org/labkey/announcements/AnnouncementsController.java
+++ b/announcements/src/org/labkey/announcements/AnnouncementsController.java
@@ -1071,7 +1071,7 @@ public BaseInsertView(String page, InsertBean bean, AnnouncementForm form, URLHe
if (reshow)
{
- currentRendererType = EnumUtils.getEnum(WikiRendererType.class, form.get("rendererType"), DEFAULT_MESSAGE_RENDERER_TYPE);
+ currentRendererType = EnumUtils.getEnum(WikiRendererType.class, form.getAsString("rendererType"), DEFAULT_MESSAGE_RENDERER_TYPE);
AnnouncementModel ann = form.getBean();
assignedTo = ann.getAssignedTo();
@@ -1084,18 +1084,18 @@ else if (null == latestPost)
cal.add(Calendar.MONTH, 1);
String expires = DateUtil.formatDate(c, cal.getTime());
- form.set("expires", expires);
+ form.setValueToBind("expires", expires);
currentRendererType = DEFAULT_MESSAGE_RENDERER_TYPE;
assignedTo = settings.getDefaultAssignedTo();
}
else
{
// Response... set values to match most recent properties on this thread
- assert null == form.get("title");
- assert null == form.get("expires");
+ assert null == form.getAsString("title");
+ assert null == form.getAsString("expires");
- form.set("title", latestPost.getTitle());
- form.set("status", "Active"); // By default, every new response resets status to active, #35047
+ form.setValueToBind("title", latestPost.getTitle());
+ form.setValueToBind("status", "Active"); // By default, every new response resets status to active, #35047
form.setTypedValue("expires", DateUtil.formatDate(c, latestPost.getExpires()));
assignedTo = latestPost.getAssignedTo();
@@ -1103,12 +1103,12 @@ else if (null == latestPost)
}
bean.assignedToSelect = getAssignedToSelect(c, assignedTo, "assignedTo", getViewContext().getUser());
- bean.statusSelect = getStatusSelect(form.get("status"));
+ bean.statusSelect = getStatusSelect(form.getAsString("status"));
bean.renderAsSelect = getRenderAsSelect(currentRendererType);
bean.settings = settings;
User u = form.getUser() == null ? getViewContext().getUser() : form.getUser();
- bean.memberList = getMemberList(u, c, latestPost, reshow ? form.get("memberList") : null);
+ bean.memberList = getMemberList(u, c, latestPost, reshow ? form.getAsString("memberList") : null);
bean.form = form;
bean.cancelURL = cancelURL;
@@ -1642,7 +1642,7 @@ public AnnouncementForm()
// XXX: change return value to typed GuidString
public String getParentId()
{
- return _stringValues.get("parentid");
+ return getAsString("parentid");
}
AnnouncementModel selectAnnouncement()
@@ -1671,7 +1671,7 @@ public void validate(Errors errors)
// Validate "expires" conversion from String to Date
try
{
- String expires = StringUtils.trimToNull(get("expires"));
+ String expires = StringUtils.trimToNull(getAsString("expires"));
if (null != expires)
DateUtil.parseDateTime(expires);
}
@@ -2270,7 +2270,7 @@ public ThreadView(Container c, ActionURL url, AnnouncementModel ann, Permissions
public ThreadView(AnnouncementForm form, Container c, ActionURL url, Permissions perm, boolean print)
{
this();
- AnnouncementModel ann = findThread(c, form.get("rowId"), form.get("entityId"));
+ AnnouncementModel ann = findThread(c, form.getAsString("rowId"), form.getAsString("entityId"));
init(c, ann, url, perm, false, print);
}
@@ -2514,7 +2514,7 @@ public class UpdateBean
private UpdateBean(AnnouncementForm form, AnnouncementModel ann)
{
Container c = form.getContainer();
- String reshowMemberList = form.get("memberList");
+ String reshowMemberList = form.getAsString("memberList");
annModel = ann;
settings = getSettings(c);
diff --git a/announcements/src/org/labkey/announcements/insert.jsp b/announcements/src/org/labkey/announcements/insert.jsp
index 0c206146d6f..17b6c270e6b 100644
--- a/announcements/src/org/labkey/announcements/insert.jsp
+++ b/announcements/src/org/labkey/announcements/insert.jsp
@@ -70,7 +70,7 @@
%>
<% addHandler("body", "change", "LABKEY.setDirty(true);"); %>
-
+
@@ -165,7 +165,7 @@ else
%><%= generateBackButton("Cancel") %><%
}
%>
-
+
<%
diff --git a/announcements/src/org/labkey/announcements/respond.jsp b/announcements/src/org/labkey/announcements/respond.jsp
index 43df10ae78e..4eb1b0345ef 100644
--- a/announcements/src/org/labkey/announcements/respond.jsp
+++ b/announcements/src/org/labkey/announcements/respond.jsp
@@ -75,11 +75,11 @@ if (!mr.isApproved(c, user, false /* Not a new thread */))
if (settings.isTitleEditable())
{
- %>
| Title * <%=helpPopup("Title", "This field is required.") %> | |
<%
+ %>
| Title * <%=helpPopup("Title", "This field is required.") %> | |
<%
}
else
{
- %>
|
<%
+ %>
|
<%
}
if (settings.hasStatus())
@@ -111,7 +111,7 @@ if (settings.hasMemberList())
if (settings.hasExpires())
{
- %>
| Expires | | Expired messages are not deleted, they are just no longer shown on the Portal page. |
<%
+ %>
| Expires | | Expired messages are not deleted, they are just no longer shown on the Portal page. |
<%
}
%>
@@ -129,7 +129,7 @@ if (settings.hasExpires())
<% addHandler("body", "change", "LABKEY.setDirty(true);"); %>
-
+
diff --git a/api/src/org/labkey/api/action/BaseViewAction.java b/api/src/org/labkey/api/action/BaseViewAction.java
index fcbef9eb7e2..c648b33afee 100644
--- a/api/src/org/labkey/api/action/BaseViewAction.java
+++ b/api/src/org/labkey/api/action/BaseViewAction.java
@@ -268,13 +268,13 @@ private boolean hasStringValue(String propertyName)
}
if (o instanceof String s)
{
- return null != StringUtils.trimToNull(s);
+ return !StringUtils.isBlank(s);
}
if (o instanceof String[] strings)
{
for (String s : strings)
{
- if (null != StringUtils.trimToNull(s))
+ if (!StringUtils.isBlank(s))
{
return true;
}
diff --git a/api/src/org/labkey/api/assay/actions/UploadWizardAction.java b/api/src/org/labkey/api/assay/actions/UploadWizardAction.java
index b4564122006..ead3f9259c8 100644
--- a/api/src/org/labkey/api/assay/actions/UploadWizardAction.java
+++ b/api/src/org/labkey/api/assay/actions/UploadWizardAction.java
@@ -909,7 +909,7 @@ public void renderInputHtml(RenderContext ctx, HtmlWriter out, Object value)
protected Object getInputValue(RenderContext ctx)
{
TableViewForm viewForm = ctx.getForm();
- return viewForm.getStrings().get(_inputName);
+ return viewForm.getValuesToBind().get(_inputName);
}
}
diff --git a/api/src/org/labkey/api/data/BeanViewForm.java b/api/src/org/labkey/api/data/BeanViewForm.java
index c3b7c1b6b0a..8d7677d991b 100644
--- a/api/src/org/labkey/api/data/BeanViewForm.java
+++ b/api/src/org/labkey/api/data/BeanViewForm.java
@@ -19,13 +19,16 @@
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.DynaBean;
+import org.apache.commons.beanutils.DynaClass;
+import org.labkey.api.action.HasBindParameters;
import java.util.HashMap;
import java.util.Map;
-public class BeanViewForm
extends TableViewForm implements DynaBean
+public class BeanViewForm extends TableViewForm implements DynaBean, HasBindParameters
{
+ private final StringBeanDynaClass _dynaClass;
private final Class _wrappedClass;
protected BeanViewForm(Class clss)
@@ -39,9 +42,10 @@ public BeanViewForm(Class clss, TableInfo tinfo)
}
- public BeanViewForm(Class clss, TableInfo tinfo, Map extraProps)
+ public BeanViewForm(Class clss, TableInfo tinfo, Map> extraProps)
{
- super(StringBeanDynaClass.createDynaClass(clss, extraProps), tinfo);
+ super(tinfo);
+ _dynaClass = StringBeanDynaClass.createDynaClass(clss, extraProps);
_wrappedClass = clss;
}
@@ -60,7 +64,7 @@ public K getBean()
else
bean = (K) BeanUtils.cloneBean(_oldValues);
- factory.fromMap(bean, getStrings());
+ factory.fromMap(bean, getValuesToBind());
return bean;
}
catch (ReflectiveOperationException x)
@@ -71,7 +75,7 @@ public K getBean()
else
{
ObjectFactory factory = ObjectFactory.Registry.getFactory(_wrappedClass);
- return factory.fromMap(getStrings());
+ return factory.fromMap(getValuesToBind());
}
}
@@ -83,11 +87,11 @@ public void setBean(K bean)
}
@Override
- public Map getStrings()
+ public Map getValuesToBind()
{
//If we don't have strings and do have typed values then
//make the strings match the typed values
- Map strings = super.getStrings();
+ Map strings = super.getValuesToBind();
if (null == strings || strings.isEmpty() && (null != _values && !_values.isEmpty()))
{
strings = new HashMap<>();
@@ -95,7 +99,7 @@ public Map getStrings()
{
strings.put(entry.getKey(), ConvertUtils.convert(entry.getValue()));
}
- _stringValues = strings;
+ setValuesToBind(strings);
}
return strings;
@@ -118,4 +122,68 @@ else if (o instanceof Map)
throw new IllegalArgumentException("Type of old values is incompatible with wrapped class");
}
}
+
+ @Override
+ protected Class> getTruePropType(String propName)
+ {
+ var ret = _dynaClass.getTruePropType(propName);
+ if (null == ret)
+ ret = super.getTruePropType(propName);
+ return ret;
+ }
+
+ // DynaBean
+ @Override
+ public DynaClass getDynaClass()
+ {
+ return _dynaClass;
+ }
+
+ @Override
+ public Object get(String name)
+ {
+ return super.getValueToBind(name);
+ }
+
+ @Override
+ public void set(String name, Object value)
+ {
+ super.setValueToBind(name,value);
+ }
+
+ @Override
+ public boolean contains(String arg0, String arg1)
+ {
+ throw new UnsupportedOperationException("No mapped properties in a table");
+ }
+
+ @Override
+ public Object get(String arg0, String arg1)
+ {
+ throw new UnsupportedOperationException("No mapped properties in a table");
+ }
+
+ @Override
+ public Object get(String arg0, int arg1)
+ {
+ throw new UnsupportedOperationException("No indexed properties in a table");
+ }
+
+ @Override
+ public void remove(String arg0, String arg1)
+ {
+ throw new UnsupportedOperationException("No indexed properties in a table");
+ }
+
+ @Override
+ public void set(String arg0, String arg1, Object arg2)
+ {
+ throw new UnsupportedOperationException("No mapped properties in a table");
+ }
+
+ @Override
+ public void set(String arg0, int arg1, Object arg2)
+ {
+ throw new UnsupportedOperationException("No indexed properties in a table");
+ }
}
diff --git a/api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java b/api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java
index 85d452d90a6..f6c72e9d37a 100644
--- a/api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java
+++ b/api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java
@@ -785,10 +785,15 @@ public final Class> getJavaClass()
@Override
public Class> getJavaClass(boolean isNullable)
+ {
+ return defaultJavaClass(this, isNullable);
+ }
+
+ public static Class> defaultJavaClass(ColumnRenderProperties col, boolean isNullable)
{
Class> ret;
boolean isNumeric;
- PropertyType pt = getPropertyType();
+ PropertyType pt = col.getPropertyType();
if (pt != null)
{
ret = pt.getJavaType();
@@ -796,13 +801,13 @@ public Class> getJavaClass(boolean isNullable)
}
else
{
- ret = getJdbcType().getJavaClass(isNullable);
- isNumeric = getJdbcType().isNumeric();
+ JdbcType jdbcType = col.getJdbcType();
+ ret = jdbcType.getJavaClass(isNullable);
+ isNumeric = jdbcType.isNumeric();
}
-
if (isNumeric)
{
- Unit unit = getDisplayUnit();
+ Unit unit = col.getDisplayUnit();
if (null != unit)
return unit.getQuantityClass();
}
diff --git a/api/src/org/labkey/api/data/ConvertHelper.java b/api/src/org/labkey/api/data/ConvertHelper.java
index 078b807ae9a..a8918d2bc9a 100644
--- a/api/src/org/labkey/api/data/ConvertHelper.java
+++ b/api/src/org/labkey/api/data/ConvertHelper.java
@@ -59,6 +59,7 @@
import org.labkey.api.settings.LookAndFeelProperties;
import org.labkey.api.util.DateUtil;
import org.labkey.api.util.GUID;
+import org.labkey.api.util.PageFlowUtil;
import org.labkey.api.util.ReturnURLString;
import org.labkey.api.util.SimpleTime;
import org.labkey.api.util.SkipMothershipLogging;
@@ -71,7 +72,6 @@
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
-import java.awt.*;
import java.beans.PropertyEditorSupport;
import java.io.File;
import java.math.BigDecimal;
@@ -83,7 +83,9 @@
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
@@ -146,7 +148,7 @@ protected void register()
_register(new NullSafeConverter(new CharacterConverter()), Character.class);
_register(new CharacterConverter(), Character.TYPE);
_register(new NullSafeConverter(new ClassConverter()), Class.class);
- _register(new ColorConverter(), Color.class);
+ _register(new ColorConverter(), java.awt.Color.class);
_register(new ContainerConverter(), Container.class);
_register(new GuidConverter(), GUID.class);
_register(new InfDoubleConverter(), Double.TYPE);
@@ -838,11 +840,11 @@ public static class ColorConverter implements Converter
public Object convert(Class type, Object value)
{
if (value == null)
- return Color.WHITE;
+ return java.awt.Color.WHITE;
if (value.getClass() == type)
return value;
String s = value.toString();
- return Color.decode(s);
+ return java.awt.Color.decode(s);
}
}
@@ -859,14 +861,10 @@ public Object convert(Class type, Object value)
return null;
if (value instanceof String[])
return value;
+ if (value instanceof List l)
+ return l.stream().map(v -> Objects.toString(v, null)).toArray(String[]::new);
if (value instanceof String s)
- {
- // If the value is wrapped with { and }, let the beanutils converter tokenize the values.
- // This let's us handle Issue 5340 while allowing multi-value strings to be parsed.
- if (s.startsWith("{") && s.endsWith("}"))
- return _nested.convert(type, value);
- }
-
+ return PageFlowUtil.splitStringToValuesForImport(s).toArray(String[]::new);
// Otherwise, treat it as a single element string array.
return new String[] {String.valueOf(value)};
}
diff --git a/api/src/org/labkey/api/data/DataColumn.java b/api/src/org/labkey/api/data/DataColumn.java
index e19c61ec6ef..7245fbbdb25 100644
--- a/api/src/org/labkey/api/data/DataColumn.java
+++ b/api/src/org/labkey/api/data/DataColumn.java
@@ -445,9 +445,8 @@ protected String renderURLorValueURL(RenderContext ctx)
{
// See if the value is itself a URL
Object value = getDisplayValue(ctx);
- if (value != null)
+ if (value instanceof String toString)
{
- String toString = value.toString();
if (StringUtilsLabKey.startsWithURL(toString) &&
!toString.contains(" ") &&
!toString.contains("\n") &&
@@ -798,7 +797,7 @@ protected void renderSelectFormInputFromFk(RenderContext ctx, HtmlWriter out, St
if (viewForm != null && viewForm.contains(this, ctx))
{
// On error reshow, use the user supplied form value
- displayValue = viewForm.get(formFieldName);
+ displayValue = viewForm.getAsString(formFieldName);
}
if (displayValue == null)
displayValue = getDisplayValue(ctx);
diff --git a/api/src/org/labkey/api/data/DataRegion.java b/api/src/org/labkey/api/data/DataRegion.java
index e5bac1b8929..a42b78f3dab 100644
--- a/api/src/org/labkey/api/data/DataRegion.java
+++ b/api/src/org/labkey/api/data/DataRegion.java
@@ -2058,7 +2058,7 @@ private void renderInputForm(RenderContext ctx, HtmlWriter out)
{
TableViewForm form = ctx.getForm();
if (null != form)
- ctx.setRow((Map) form.getStrings());
+ ctx.setRow(form.getValuesToBind());
}
renderForm(ctx, out);
}
@@ -2309,7 +2309,7 @@ private void renderForm(RenderContext ctx, HtmlWriter out)
//UNDONE: Should we require a viewForm whenever someone
//posts? I tend to think so.
if (null != viewForm)
- pkVal = viewForm.get(pkColName);
+ pkVal = viewForm.getAsString(pkColName);
if (pkVal == null)
pkVal = valueMap.get(pkColName);
diff --git a/api/src/org/labkey/api/data/DisplayColumn.java b/api/src/org/labkey/api/data/DisplayColumn.java
index 9a582246844..2d673af6256 100644
--- a/api/src/org/labkey/api/data/DisplayColumn.java
+++ b/api/src/org/labkey/api/data/DisplayColumn.java
@@ -1163,7 +1163,7 @@ protected Object getInputValue(RenderContext ctx)
if (viewForm.hasTypedValue(formFieldName))
val = viewForm.getTypedValue(formFieldName);
else
- val = viewForm.get(formFieldName);
+ val = viewForm.getAsString(formFieldName);
}
else if (ctx.getRow() != null)
val = col.getValue(ctx);
diff --git a/api/src/org/labkey/api/data/MVDisplayColumn.java b/api/src/org/labkey/api/data/MVDisplayColumn.java
index af465ddca98..c60b6907c66 100644
--- a/api/src/org/labkey/api/data/MVDisplayColumn.java
+++ b/api/src/org/labkey/api/data/MVDisplayColumn.java
@@ -164,8 +164,8 @@ protected Object getInputValue(RenderContext ctx)
if (col != null)
{
String formFieldName = ctx.getForm().getFormFieldName(col);
- if (null != viewForm && viewForm.getStrings().containsKey(formFieldName))
- val = viewForm.get(formFieldName);
+ if (null != viewForm && viewForm.getValuesToBind().containsKey(formFieldName))
+ val = viewForm.getAsString(formFieldName);
else if (ctx.getRow() != null)
{
val = getRawValue(ctx);
diff --git a/api/src/org/labkey/api/data/StringBeanDynaClass.java b/api/src/org/labkey/api/data/StringBeanDynaClass.java
index 0782941e9e1..67259bcd485 100644
--- a/api/src/org/labkey/api/data/StringBeanDynaClass.java
+++ b/api/src/org/labkey/api/data/StringBeanDynaClass.java
@@ -38,10 +38,10 @@ protected StringBeanDynaClass(Class> beanClass)
this(beanClass, null);
}
- protected StringBeanDynaClass(Class beanClass, Map extras)
+ protected StringBeanDynaClass(Class> beanClass, Map> extras)
{
_beanClass = beanClass;
- PropertyDescriptor propDescriptors[] = PropertyUtils.getPropertyDescriptors(beanClass);
+ PropertyDescriptor[] propDescriptors = PropertyUtils.getPropertyDescriptors(beanClass);
if (propDescriptors == null)
propDescriptors = new PropertyDescriptor[0];
Map> propTypes = new CaseInsensitiveHashMap<>();
@@ -49,7 +49,7 @@ protected StringBeanDynaClass(Class beanClass, Map extras)
propTypes.put(propDescriptor.getName(), propDescriptor.getPropertyType());
if (null != extras)
{
- for (Map.Entry entry : extras.entrySet())
+ for (Map.Entry> entry : extras.entrySet())
{
String prop = entry.getKey();
if (propTypes.containsKey(prop))
@@ -70,38 +70,14 @@ protected StringBeanDynaClass(Class beanClass, Map extras)
*
* @param beanClass Bean class for which a WrapDynaClass is requested
*/
- public static StringBeanDynaClass createDynaClass(Class beanClass)
+ public static StringBeanDynaClass createDynaClass(Class> beanClass)
{
-
- /*
-
- WrapStringDynaClass dynaClass =
- (WrapStringDynaClass) _dynaClasses.get(beanClass);
- if (dynaClass == null)
- {
- dynaClass = new WrapStringDynaClass(beanClass);
- _dynaClasses.put(beanClass, dynaClass);
- }
- return (dynaClass);
- */
return new StringBeanDynaClass(beanClass);
}
- public static StringBeanDynaClass createDynaClass(Class beanClass, Map extraProps)
+ public static StringBeanDynaClass createDynaClass(Class> beanClass, Map> extraProps)
{
-
- /*
-
- WrapStringDynaClass dynaClass =
- (WrapStringDynaClass) _dynaClasses.get(beanClass);
- if (dynaClass == null)
- {
- dynaClass = new WrapStringDynaClass(beanClass);
- _dynaClasses.put(beanClass, dynaClass);
- }
- return (dynaClass);
- */
return new StringBeanDynaClass(beanClass, extraProps);
}
@@ -117,6 +93,6 @@ public Class> getBeanClass()
@Override
public DynaBean newInstance()
{
- return new BeanViewForm(_beanClass);
+ throw new UnsupportedOperationException("StringBeanDynaClass does not support newInstance()");
}
}
diff --git a/api/src/org/labkey/api/data/TableViewForm.java b/api/src/org/labkey/api/data/TableViewForm.java
index fd7aae09fbb..3f82dc0cc88 100644
--- a/api/src/org/labkey/api/data/TableViewForm.java
+++ b/api/src/org/labkey/api/data/TableViewForm.java
@@ -19,10 +19,7 @@
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.ConvertUtils;
-import org.apache.commons.beanutils.DynaBean;
-import org.apache.commons.beanutils.DynaClass;
import org.apache.commons.beanutils.PropertyUtils;
-import org.apache.commons.collections4.IteratorUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
@@ -39,12 +36,14 @@
import org.labkey.api.security.permissions.InsertPermission;
import org.labkey.api.security.permissions.Permission;
import org.labkey.api.security.permissions.UpdatePermission;
+import org.labkey.api.util.PageFlowUtil;
import org.labkey.api.util.logging.LogHelper;
import org.labkey.api.view.ActionURL;
import org.labkey.api.view.NotFoundException;
import org.labkey.api.view.UnauthorizedException;
import org.labkey.api.view.ViewContext;
import org.labkey.api.view.ViewForm;
+import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.validation.BindException;
@@ -54,26 +53,31 @@
import java.beans.Introspector;
import java.io.File;
+import java.lang.reflect.Array;
import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Basic form for handling posts into views.
* Supports insert, update, delete functionality with a minimum of fuss
*
*/
-public class TableViewForm extends ViewForm implements DynaBean, HasBindParameters
+public class TableViewForm extends ViewForm implements HasBindParameters
{
private static final Logger _log = LogHelper.getLogger(TableViewForm.class, "Table operation warnings");
- protected Map _stringValues = new CaseInsensitiveHashMap<>();
+ // This is called "stringValues" as this is expected to come from a form POST (but it was never just a string value)
+ // However, it can also be String[] and other types
+ protected Map _stringValues = new CaseInsensitiveHashMap<>();
protected Map _values = null;
- protected StringWrapperDynaClass _dynaClass;
protected Object _oldValues;
protected TableInfo _tinfo = null;
protected String[] _selectedRows = null;
@@ -85,19 +89,12 @@ public class TableViewForm extends ViewForm implements DynaBean, HasBindParamete
public static final String DATA_SUBMIT_NAME = ".dataSubmit";
public static final String BULK_UPDATE_NAME = ".bulkUpdate";
- /**
- * Creates a TableViewForm with no underlying dynaclass.
- */
+
protected TableViewForm()
{
super();
}
- public TableViewForm(StringWrapperDynaClass dynaClass)
- {
- super();
- _dynaClass = dynaClass;
- }
/**
* Creates a view form that wraps a table.
@@ -107,25 +104,9 @@ public TableViewForm(@NotNull TableInfo tinfo)
setTable(tinfo);
}
- /**
- * Creates a view form that uses the supplied dynaClass for the property
- * list, but stashes the tableInfo for insert/update purposes and
- * to perform additional validation.
- */
- public TableViewForm(StringWrapperDynaClass dynaClass, TableInfo tinfo)
- {
- _dynaClass = dynaClass;
- _tinfo = tinfo;
- }
-
- /**
- * Sets the table. NOTE This will also overwrite any previously
- * set dynaClass with one derived from the table.
- */
protected void setTable(@NotNull TableInfo tinfo)
{
_tinfo = tinfo;
- _dynaClass = TableWrapperDynaClass.getDynaClassInstance(tinfo);
}
public TableInfo getTable()
@@ -160,7 +141,7 @@ public void doInsert() throws SQLException
throw new UnauthorizedException();
}
if (null != _tinfo.getColumn("container"))
- set("container", _c.getId());
+ setValueToBind("container", _c.getId());
Map newMap = Table.insert(_user, _tinfo, getTypedValues());
setTypedValues(newMap, false);
@@ -184,7 +165,7 @@ public void doUpdate() throws SQLException
}
if (null != _tinfo.getColumn("container"))
- set("container", _c.getId());
+ setValueToBind("container", _c.getId());
Object[] pkVal = getPkVals();
Map newMap = Table.update(_user, _tinfo, getTypedValues(), pkVal);
@@ -296,14 +277,12 @@ public List getPkNamesList()
public void setPkVal(String str)
{
assertSinglePK();
-
- set(getPkName(), str);
+ setValueToBind(getPkName(), str);
}
public void setPkVal(Object o)
{
assertSinglePK();
-
setTypedValue(getPkName(), o);
}
@@ -318,16 +297,22 @@ public void setPkVals(String s)
{
//Issue 42042: Lists with text primary key don't handle commas in key value when viewing row details
if (getPkNamesList().size() == 1)
- set(getPkNamesList().get(0), s);
+ {
+ setValueToBind(getPkNamesList().get(0), s);
+ }
else
+ {
+ // CONSIDER We should support PK column names with commas. We should replace with better parser.
+ // something like: setPkVals(PageFlowUtil.splitStringToValuesForImport(s));
setPkVals(s.split(","));
+ }
}
public void setPkVals(String[] s)
{
List pkNames = getPkNamesList();
for (int i = 0; i < pkNames.size() && i < s.length; i++)
- set(pkNames.get(i), s[i]);
+ setValueToBind(pkNames.get(i), s[i]);
}
/**
@@ -359,13 +344,17 @@ public Object[] getPkVals()
{
Object oldValues = getOldValues();
if (oldValues instanceof Map m)
+ {
pkVal = m.get(pkName);
+ }
else
+ {
try
{
pkVal = PropertyUtils.getProperty(oldValues, pkName);
}
catch (Exception ignored) {}
+ }
}
pkVals[i] = pkVal;
}
@@ -387,16 +376,26 @@ public BindException populateValues(BindException errors)
return errors;
}
+
public void setValidateRequired(boolean validateRequired)
{
_validateRequired = validateRequired;
}
- protected void _populateValues(BindException errors)
+
+ public Object getValueToBind(String propName)
{
- // Don't do anything special if dynaclass is null
- assert _dynaClass != null;
+ Object value = _stringValues.get(propName);
+ if (null == value)
+ return null;
+ if (value instanceof String str)
+ return StringUtils.trimToNull(str);
+ return value;
+ }
+
+ protected void _populateValues(BindException errors)
+ {
/*
Note that nulls in the hashmap are NOT the same as missing values
A null in the hashmap indicates an empty string was posted.
@@ -409,31 +408,29 @@ protected void _populateValues(BindException errors)
for (String propName : keys)
{
+ // NOTE later code relies on false==contains(propName) when there is a conversion error
+ Object bindValue = getValueToBind(propName);
ColumnInfo col = getColumnByFormFieldName(propName);
- String str = _stringValues.get(propName);
- String caption = _dynaClass.getPropertyCaption(propName);
+ String caption = getPropertyCaption(propName);
Class> propType = null;
- if (StringUtils.isEmpty(str))
- str = null;
-
try
{
-
- if (null != str)
+ if (null != bindValue)
{
+ propType = getTruePropType(propName);
Object val;
if (null != col && null != col.getKindOfQuantity())
{
- val = Quantity.convert(str, col.getDisplayUnit());
+ // TODO MultiChoice switch to col.getConvertFn().apply(bindValue)
+ val = Quantity.convert(bindValue, col.getDisplayUnit());
}
else
{
- propType = _dynaClass.getTruePropType(propName);
if (propType != null)
- val = ConvertUtils.convert(str, propType);
+ val = ConvertUtils.convert(bindValue, propType);
else
- val = str;
+ val = bindValue;
}
values.put(propName, val);
}
@@ -455,7 +452,7 @@ else if (_validateRequired && null != _tinfo)
if (mvCol != null)
{
String ff_mvName = getFormFieldName(mvCol);
- isError = StringUtils.trimToNull(_stringValues.get(ff_mvName)) == null;
+ isError = null == getValueToBind(ff_mvName);
}
}
if (isError)
@@ -463,7 +460,6 @@ else if (_validateRequired && null != _tinfo)
else
values.put(propName, null);
}
-
}
else
{
@@ -482,6 +478,7 @@ else if (_validateRequired && null != _tinfo)
Container container = fk.getLookupContainer() != null ? fk.getLookupContainer() : getContainer();
try
{
+ String str = null==bindValue ? null : bindValue instanceof String[] ? ((String[])bindValue)[0] : (String)bindValue;
Object remappedValue = cache.remap(fk.getLookupSchemaKey(), fk.getLookupTableName(), getUser(), container, ContainerFilter.Type.CurrentPlusProjectAndShared, str);
if (remappedValue != null)
{
@@ -500,6 +497,7 @@ else if (_validateRequired && null != _tinfo)
String error = SpringActionController.ERROR_CONVERSION;
if (null != propType)
error += "." + propType.getSimpleName();
+ String str = bindValue instanceof String[] strs ? PageFlowUtil.joinValuesToString(Arrays.asList(strs),',') : String.valueOf(bindValue);
errors.addError(new FieldError(errors.getObjectName(), propName, this, true, new String[] {error}, new String[] {str, caption}, Objects.toString(defaultMessage, "Could not convert value: " + str)));
}
}
@@ -536,8 +534,12 @@ public boolean hasTypedValue(ColumnInfo column)
public void setTypedValue(String propName, Object val)
{
- getTypedValues().put(propName, val);
- _stringValues.put(propName, ConvertUtils.convert(val));
+ // call _populate() if necessary
+ getTypedValues();
+ _values.put(propName, val);
+ // We don't use setValueToBind() here because we want to avoid its side effect of clearing _values
+ // To convert or not to convert???
+ _stringValues.put(propName, val);
}
/**
@@ -550,14 +552,10 @@ public void setTypedValue(String propName, Object val)
*/
public Map getTypedValues()
{
- // Don't have values if dynaclass is null
- if (null == _dynaClass)
- return null;
-
if (null == _values)
populateValues(null);
- return _values;
+ return Collections.unmodifiableMap(_values);
}
/**
@@ -572,9 +570,13 @@ public CaseInsensitiveHashMap