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 @@ %>Note: This <%=h(settings.getConversationName().toLowerCase())%> will not be posted immediately; it will appear after the content has been reviewed.

<% } %> - Title * <%= helpPopup("Title", "This field is required.") %> + Title * <%= helpPopup("Title", "This field is required.") %> <% if (settings.hasStatus()) { @@ -101,7 +101,7 @@ } if (settings.hasExpires()) { - %>ExpiresBy default the Expires field is set to one month from today.
Expired messages are not deleted, they are just no longer shown on the Portal page.
<% + %>ExpiresBy default the Expires field is set to one month from today.
Expired messages are not deleted, they are just no longer shown on the Portal page.
<% } %> @@ -119,7 +119,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()) { - %>ExpiresExpired messages are not deleted, they are just no longer shown on the Portal page.<% + %>ExpiresExpired 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 getTypedColumns(boolean includeUntyped) for (ColumnInfo column : getTable().getColumns()) { if (hasTypedValue(column)) + { values.put(column.getName(), getTypedValue(column)); - else if (includeUntyped && contains(column)) - values.put(column.getName(), get(column)); + } + else if (includeUntyped && _stringValues.containsKey(getFormFieldName(column))) + { + values.put(column.getName(), _stringValues.get(getFormFieldName(column))); + } else if (getRequest() instanceof MultipartHttpServletRequest request) { String fieldName = getMultiPartFormFieldName(column); @@ -603,8 +605,8 @@ else if (File.class.equals(column.getJavaClass())) { if (hasTypedValue(mvColumn)) values.put(mvColumn.getName(), getTypedValue(mvColumn)); - else if (includeUntyped && contains(mvColumn)) - values.put(mvColumn.getName(), get(mvColumn)); + else if (includeUntyped && _stringValues.containsKey(getFormFieldName(mvColumn))) + values.put(mvColumn.getName(), _stringValues.get(getFormFieldName(mvColumn))); } } } @@ -626,7 +628,6 @@ public CaseInsensitiveHashMap getTypedColumns() */ public void setTypedValues(Map values, boolean merge) { - assert null != _dynaClass; values = Collections.unmodifiableMap(values); //We assume this means data is loaded. @@ -642,27 +643,23 @@ public void setTypedValues(Map values, boolean merge) String propName = e.getKey(); if (Character.isUpperCase(propName.charAt(0))) propName = Introspector.decapitalize(propName); - _values.put(propName, e.getValue()); - _stringValues.put(propName, ConvertUtils.convert(e.getValue())); + setTypedValue(propName, e.getValue()); + // TODO MultiChoice To convert or not to convert??? + _stringValues.put(propName, e.getValue()); } } - public void setStrings(Map strings) + public void setValuesToBind(Map strings) { - assert null != _dynaClass; - - _stringValues = strings; + _stringValues.clear(); _values = null; + for (Map.Entry e : strings.entrySet()) + setValueToBind(e.getKey(), e.getValue()); } - public Map getStrings() - { - return _stringValues; - } - - public boolean contains(ColumnInfo col) + public Map getValuesToBind() { - return _stringValues.containsKey(getFormFieldName(col)); + return Collections.unmodifiableMap(_stringValues); } public boolean contains(DisplayColumn col, RenderContext ctx) @@ -670,80 +667,35 @@ public boolean contains(DisplayColumn col, RenderContext ctx) return _stringValues.containsKey(col.getFormFieldName(ctx)); } - @Override - public String get(String arg0) - { - return _stringValues.get(arg0); - } - - public String get(ColumnInfo col) + public @Nullable String getAsString(@NotNull String propName) { - return _stringValues.get(getFormFieldName(col)); - } - - @Override - public void set(String arg0, Object arg1) - { - String v; - if (arg1 == null) - v = null; - else if (arg1 instanceof Object[]) - { - // HACK: This is annoying, but TableViewForm insists on converting values to Strings before letting populateValues() bind. - // Doubly annoying is we need to work around StringArrayConverter's poor parsing of single string values as seen in Issue 5340. - // Convert into stringified array that org.apache.commons.beanutils.converters.StringArrayConverter can parse. - v = "{" + StringUtils.join((Object[])arg1, ",") + "}"; - } - else + Object value = _stringValues.get(propName); + if (value == null || value instanceof String) + return (String)value; + if (value instanceof String[] arr) { - // Trim to prevent users from inadvertently letting in leading/trailing spaces, which cause confusion on filtering, sorting, joins, and many other places - v = arg1.toString().trim(); + if (arr.length == 0) + return null; } - _stringValues.put(arg0, v); - _values = null; - } - - @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"); + return ConvertUtils.convert(value); } - @Override - public DynaClass getDynaClass() + public String getAsString(ColumnInfo col) { - return _dynaClass; + return getAsString(getFormFieldName(col)); } - @Override - public void remove(String arg0, String arg1) + public void setValueToBind(String propName, Object value) { - 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"); + if (null == value || value instanceof String || value instanceof String[]) + _stringValues.put(propName, value); + else if (value instanceof Collection col && col.stream().allMatch(e -> null==e || e instanceof String)) + _stringValues.put(propName, col.toArray(new String[0])); + else + _stringValues.put(propName, ConvertUtils.convert(value)); + _values = null; } - @Override - public void set(String arg0, int arg1, Object arg2) - { - throw new UnsupportedOperationException("No indexed properties in a table"); - } public void validateBind(BindException errors) { @@ -765,12 +717,38 @@ public void setOldValues(Object oldValues) public void forceReselect() { Object[] pk = getPkVals(); - setStrings(new HashMap<>()); + setValuesToBind(new HashMap<>()); setOldValues(null); setPkVals(pk); setDataLoaded(false); } + + protected Class getTruePropType(String propName) + { + ColumnInfo column = getColumnByFormFieldName(propName); + if (null == column) + return null; + // TODO MultiChoice : move this to ColumnInfo (it does not belong in this one place) + // TODO MultiChoice : Can we actually assume that the FK column (in this table) is the same type as the lookup column? + boolean multiValued = column.getFk() instanceof MultiValuedForeignKey && ((MultiValuedForeignKey)column.getFk()).isMultiSelectInput(); + if (multiValued) + return arrayClass(column.getJavaClass()); + return column.getJavaClass(); + } + + private static Class arrayClass(Class k) + { + Object o = Array.newInstance(k, 0); + return o.getClass(); + } + + private String getPropertyCaption(String propName) + { + ColumnInfo column = getColumnByFormFieldName(propName); + return null==column ? propName : column.getLabel(); + } + public String getFormFieldName(@NotNull ColumnInfo column) { return column.getName(); @@ -826,27 +804,53 @@ public void setViewContext(@NotNull ViewContext context) } } - @Override - public @NotNull BindException bindParameters(PropertyValues params) + /** Handle @ prefix and [] suffix + * "@field" indicates that if "field" is missing, it should be treated as "field=0" + * "@field[] indicates that value should be treated as an array even if only one value is present + *
+ * client _could_ post both "myfield=" and "myfield[]=", but that's a client bug + */ + public static PropertyValues preprocessPropertyValues(PropertyValues params) { - /* - * Checkboxes are weird. If set to FALSE they don't post - * at all. So impossible to tell difference between values - * that weren't on the html form at all and ones that were set to false - * by the user. - * To fix this each checkbox posts its name in a hidden field - * We set them all to false and spring will overwrite with true - * if they are set. - */ - HttpServletRequest request = getRequest(); + // we can usually just return params + if (params.stream().noneMatch(e -> e.getName().endsWith("[]") || e.getName().startsWith(SpringActionController.FIELD_MARKER))) + return params; - // handle Spring style markers - IteratorUtils.asIterator(request.getParameterNames()).forEachRemaining(name -> { - if (name.startsWith(SpringActionController.FIELD_MARKER)) - set(name.substring(SpringActionController.FIELD_MARKER.length()), "0"); - }); + Set names = params.stream().map(PropertyValue::getName).collect(Collectors.toSet()); + var ret = new MutablePropertyValues(); + for (var orig : params) + { + var copy = orig; + if (orig.getName().startsWith(SpringActionController.FIELD_MARKER)) + { + if (names.contains(orig.getName().substring(1))) + continue; + copy = new PropertyValue(orig.getName().substring(1), "0"); + } + else if (orig.getName().endsWith("[]") && orig.getValue()!=null) + { + var value = orig.getValue(); + var convertedValue = value; + if (List.class.isAssignableFrom(value.getClass())) + { + convertedValue = ((List) value).toArray(new Object[0]); + } + if (!value.getClass().isArray()) + { + convertedValue = Array.newInstance(value.getClass(), 1); + Array.set(convertedValue, 0, value); + } + copy = new PropertyValue(orig.getName().substring(0, orig.getName().length() - 2), convertedValue); + } + ret.addPropertyValue(copy); + } + return ret; + } - BindException errors = new NullSafeBindException(new BaseViewAction.BeanUtilsPropertyBindingResult(this, "form")); + @Override + public @NotNull BindException bindParameters(PropertyValues paramsIn) + { + var params = preprocessPropertyValues(paramsIn); // handle binding of base class ReturnURLForm PropertyValue pvReturn = params.getPropertyValue(ActionURL.Param.returnUrl.toString()); @@ -861,11 +865,10 @@ public void setViewContext(@NotNull ViewContext context) for (PropertyValue pv : params.getPropertyValues()) { - Object value = pv.getValue(); - if (value instanceof String || value instanceof String[]) - set(pv.getName(), value); + setValueToBind(pv.getName(), pv.getValue()); } + BindException errors = new NullSafeBindException(new BaseViewAction.BeanUtilsPropertyBindingResult(this, "form")); validateBind(errors); return errors; } diff --git a/api/src/org/labkey/api/data/TableWrapperDynaClass.java b/api/src/org/labkey/api/data/TableWrapperDynaClass.java deleted file mode 100644 index 07e07232e05..00000000000 --- a/api/src/org/labkey/api/data/TableWrapperDynaClass.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2004-2018 Fred Hutchinson Cancer Research Center - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.labkey.api.data; - -import org.apache.commons.beanutils.DynaBean; -import org.jetbrains.annotations.NotNull; -import org.labkey.api.collections.CaseInsensitiveHashMap; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Creates a DynaClass for a table where all properties are strings. - */ -public class TableWrapperDynaClass extends StringWrapperDynaClass -{ - private final TableInfo _tinfo; - private static final Map _dynClasses = new ConcurrentHashMap<>(); - - private TableWrapperDynaClass(TableInfo tinfo) - { - _tinfo = tinfo; - List cols = tinfo.getColumns(); - Map> propMap = new CaseInsensitiveHashMap<>(); - for (ColumnInfo col : cols) - propMap.put(col.getName(), col.getJavaClass()); - - init(tinfo.getName(), propMap); - } - - public static TableWrapperDynaClass getDynaClassInstance(@NotNull TableInfo tinfo) - { - TableWrapperDynaClass tdc = _dynClasses.get(tinfo); - if (null == tdc) - { - tdc = new TableWrapperDynaClass(tinfo); - if (tinfo instanceof SchemaTableInfo) - _dynClasses.put(tinfo, tdc); - } - return tdc; - } - - public TableInfo getTable() - { - return _tinfo; - } - - @Override - public String getName() - { - return _tinfo.getName(); - } - - @Override - public DynaBean newInstance() - { - return new TableViewForm(_tinfo); - } -} diff --git a/api/src/org/labkey/api/exp/SamplePropertyHelper.java b/api/src/org/labkey/api/exp/SamplePropertyHelper.java index b259e01d0f7..1a41a77318b 100644 --- a/api/src/org/labkey/api/exp/SamplePropertyHelper.java +++ b/api/src/org/labkey/api/exp/SamplePropertyHelper.java @@ -246,7 +246,7 @@ public boolean isEditable() protected Object getInputValue(RenderContext ctx) { TableViewForm viewForm = ctx.getForm(); - return viewForm.getStrings().get(getName()); + return viewForm.getValuesToBind().get(getName()); } @Override diff --git a/api/src/org/labkey/api/query/QueryUpdateForm.java b/api/src/org/labkey/api/query/QueryUpdateForm.java index f3ad03b2360..c2585120947 100644 --- a/api/src/org/labkey/api/query/QueryUpdateForm.java +++ b/api/src/org/labkey/api/query/QueryUpdateForm.java @@ -51,8 +51,7 @@ public QueryUpdateForm(@NotNull TableInfo table, @NotNull ViewContext ctx, boole public QueryUpdateForm(@NotNull TableInfo table, @NotNull ViewContext ctx, @Nullable BindException errors) { - _tinfo = table; - _dynaClass = new QueryWrapperDynaClass(this); + super(table); setViewContext(ctx); // TODO: Fix this hack. diff --git a/api/src/org/labkey/api/query/QueryWrapperDynaClass.java b/api/src/org/labkey/api/query/QueryWrapperDynaClass.java deleted file mode 100644 index dfa346e8470..00000000000 --- a/api/src/org/labkey/api/query/QueryWrapperDynaClass.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2008-2019 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.labkey.api.query; - -import org.apache.commons.beanutils.DynaBean; -import org.jetbrains.annotations.NotNull; -import org.labkey.api.collections.CaseInsensitiveHashMap; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.MultiValuedForeignKey; -import org.labkey.api.data.StringWrapperDynaClass; -import org.labkey.api.data.TableInfo; - -import java.lang.reflect.Array; -import java.util.Map; - -public class QueryWrapperDynaClass extends StringWrapperDynaClass -{ - QueryUpdateForm _form; - - public QueryWrapperDynaClass(@NotNull QueryUpdateForm form) - { - _form = form; - TableInfo table = form.getTable(); - if (table == null) - throw new IllegalArgumentException(); - - // CONSIDER: Handle MultiValueFK in column.getJavaClass() directly - Map> propMap = new CaseInsensitiveHashMap<>(); - for (ColumnInfo column : table.getColumns()) - { - boolean multiValued = column.getFk() instanceof MultiValuedForeignKey && ((MultiValuedForeignKey)column.getFk()).isMultiSelectInput(); - if (multiValued) - propMap.put(_form.getFormFieldName(column), arrayClass(column.getJavaClass())); - else - propMap.put(_form.getFormFieldName(column), column.getJavaClass()); - } - - init("className", propMap); - - } - - private static Class arrayClass(Class k) - { - Object o = Array.newInstance(k, 0); - return o.getClass(); - } - - - @Override - public DynaBean newInstance() - { - throw new UnsupportedOperationException(); - } - - @Override - public String getPropertyCaption(String propName) - { - ColumnInfo column = _form.getColumnByFormFieldName(propName); - if (column == null) - return propName; - return column.getLabel(); - } -} diff --git a/api/src/org/labkey/api/study/actions/ParticipantVisitResolverChooser.java b/api/src/org/labkey/api/study/actions/ParticipantVisitResolverChooser.java index 782523c6fdf..c9a6d34f1ef 100644 --- a/api/src/org/labkey/api/study/actions/ParticipantVisitResolverChooser.java +++ b/api/src/org/labkey/api/study/actions/ParticipantVisitResolverChooser.java @@ -201,8 +201,8 @@ protected Object getInputValue(RenderContext ctx) { TableViewForm viewForm = ctx.getForm(); // check to see if our insert view has explicit initial values: - if (null != viewForm && viewForm.getStrings().containsKey(_typeInputName)) - return viewForm.get(_typeInputName); + if (null != viewForm && viewForm.getValuesToBind().containsKey(_typeInputName)) + return viewForm.getAsString(_typeInputName); return ctx.get(_typeInputName); } } diff --git a/api/src/org/labkey/api/study/assay/FileLinkDisplayColumn.java b/api/src/org/labkey/api/study/assay/FileLinkDisplayColumn.java index 06ae841347c..970fa700288 100644 --- a/api/src/org/labkey/api/study/assay/FileLinkDisplayColumn.java +++ b/api/src/org/labkey/api/study/assay/FileLinkDisplayColumn.java @@ -227,7 +227,7 @@ protected Object getInputValue(RenderContext ctx) { if (null != viewForm && viewForm.contains(this, ctx)) { - val = viewForm.get(getFormFieldName(ctx)); + val = viewForm.getAsString(getFormFieldName(ctx)); } else if (ctx.getRow() != null) val = col.getValue(ctx); diff --git a/api/src/org/labkey/api/study/assay/thawListSelector.jsp b/api/src/org/labkey/api/study/assay/thawListSelector.jsp index bcadc951c5a..840790ab13b 100644 --- a/api/src/org/labkey/api/study/assay/thawListSelector.jsp +++ b/api/src/org/labkey/api/study/assay/thawListSelector.jsp @@ -39,10 +39,10 @@ JspView thisView = HttpView.currentView(); RenderContext ctx = thisView.getModelBean(); boolean renderAll = ctx.get(RenderSubSelectors.class.getSimpleName()) == null ? true : ctx.get(RenderSubSelectors.class.getSimpleName()).equals(RenderSubSelectors.ALL); - boolean listType = ThawListResolverType.LIST_NAMESPACE_SUFFIX.equalsIgnoreCase(ctx.getForm().get(ThawListResolverType.THAW_LIST_TYPE_INPUT_NAME)); + boolean listType = ThawListResolverType.LIST_NAMESPACE_SUFFIX.equalsIgnoreCase(ctx.getForm().getAsString(ThawListResolverType.THAW_LIST_TYPE_INPUT_NAME)); boolean textType = !listType; - String containerPath = ctx.getForm().get(ThawListResolverType.THAW_LIST_LIST_CONTAINER_INPUT_NAME); + String containerPath = ctx.getForm().getAsString(ThawListResolverType.THAW_LIST_LIST_CONTAINER_INPUT_NAME); Container container = containerPath == null ? null : ContainerManager.getForPath(containerPath); String textTypeId = "RadioBtn-" + ThawListResolverType.THAW_LIST_TYPE_INPUT_NAME + "-" + ThawListResolverType.TEXT_NAMESPACE_SUFFIX; @@ -96,7 +96,7 @@ typeAhead : true, typeAheadDelay : 250, forceSelection : true, - initialValue : <%=q(ctx.getForm().get(ThawListResolverType.THAW_LIST_LIST_SCHEMA_NAME_INPUT_NAME))%>, + initialValue : <%=q(ctx.getForm().getAsString(ThawListResolverType.THAW_LIST_LIST_SCHEMA_NAME_INPUT_NAME))%>, fieldLabel : 'Schema', name: <%= q(ThawListResolverType.THAW_LIST_LIST_SCHEMA_NAME_INPUT_NAME) %>, validateOnBlur: false, @@ -104,14 +104,14 @@ })); var queryCombo = Ext4.create('Ext.form.field.ComboBox', sqvModel.makeQueryComboConfig({ - defaultSchema : <%=q(ctx.getForm().get(ThawListResolverType.THAW_LIST_LIST_SCHEMA_NAME_INPUT_NAME))%>, + defaultSchema : <%=q(ctx.getForm().getAsString(ThawListResolverType.THAW_LIST_LIST_SCHEMA_NAME_INPUT_NAME))%>, id : 'thawListQueryName', includeUserQueries: true, typeAhead : true, typeAheadDelay : 250, fieldLabel : 'Query', name: <%= q(ThawListResolverType.THAW_LIST_LIST_QUERY_NAME_INPUT_NAME) %>, - initialValue : <%=q(ctx.getForm().get(ThawListResolverType.THAW_LIST_LIST_QUERY_NAME_INPUT_NAME))%>, + initialValue : <%=q(ctx.getForm().getAsString(ThawListResolverType.THAW_LIST_LIST_QUERY_NAME_INPUT_NAME))%>, width: 500 })); diff --git a/api/src/org/labkey/api/util/PageFlowUtil.java b/api/src/org/labkey/api/util/PageFlowUtil.java index c135c775455..feaf2515c73 100644 --- a/api/src/org/labkey/api/util/PageFlowUtil.java +++ b/api/src/org/labkey/api/util/PageFlowUtil.java @@ -24,6 +24,7 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Strings; import org.apache.logging.log4j.Logger; import org.apache.tika.detect.DefaultDetector; import org.apache.tika.io.TikaInputStream; @@ -123,6 +124,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.text.ParsePosition; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; @@ -146,7 +148,6 @@ import java.util.stream.Stream; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import static org.apache.commons.lang3.StringUtils.startsWith; import static org.labkey.api.data.DataRegion.LAST_FILTER_PARAM; import static org.labkey.api.util.DOM.A; import static org.labkey.api.util.DOM.Attribute.height; @@ -649,7 +650,7 @@ public static String wafDecode(@Nullable String encoded) if (StringUtils.isBlank(encoded)) return null; - if (StringUtils.startsWith(encoded, WAF_PREFIX)) + if (Strings.CS.startsWith(encoded, WAF_PREFIX)) { encoded = encoded.substring(WAF_PREFIX.length()); if (!Pattern.matches("[A-Za-z0-9+/=]*", encoded)) @@ -707,7 +708,7 @@ public static String wafEncode(@Nullable String plain) return ""; String encoded = URLEncoder.encode(s, StringUtilsLabKey.DEFAULT_CHARSET); - encoded = StringUtils.replace(encoded, "+", "%20"); + encoded = Strings.CS.replace(encoded, "+", "%20"); if (decodeUnreservedMarks) { @@ -715,7 +716,7 @@ public static String wafEncode(@Nullable String plain) // does not. Here we decode these marks so that we match encodeURIComponent() encoding. // See https://stackoverflow.com/a/607403 for (var entry : DECODE_UNRESERVED_MARKS.entrySet()) - encoded = StringUtils.replace(encoded, entry.getValue(), entry.getKey()); + encoded = Strings.CS.replace(encoded, entry.getValue(), entry.getKey()); } return encoded; @@ -940,7 +941,7 @@ public static void prepareResponseForFile(HttpServletResponse response, Map>> 32)); + result = 31 * result + Long.hashCode(modified); return result; } } @@ -2053,6 +2054,7 @@ public static String validateHtml(String html, Collection errors, boolea } /** validate an html fragment */ + @SuppressWarnings("HttpUrlsUsage") public static String validateHtml(String html, Collection errors, Collection scriptWarnings) { if (!errors.isEmpty() || (null != scriptWarnings && !scriptWarnings.isEmpty())) @@ -2587,6 +2589,121 @@ public static String joinValuesToString(@NotNull List values, char delim .collect(Collectors.joining(String.valueOf(delimiter))); } + + private static char peek(ParsePosition p, char[] s) + { + int i=p.getIndex(); + return i < s.length-1 ? s[i+1] : '\0'; + } + + private static char next(ParsePosition p, char[] s) + { + int i=p.getIndex(); + if (i >= s.length-1) + return '\0'; + p.setIndex(i+1); + return s[i+1]; + } + + public static List splitStringToValuesForImport(String str) + { + enum STATE {BEFORETOKEN, INTOKEN, INQUOTEDTOKEN, AFTERTOKEN} + + if (str == null) + return null; + List result = new ArrayList<>(); + StringBuilder currentToken = new StringBuilder(); + STATE state = STATE.BEFORETOKEN; + char[] charArray = str.toCharArray(); + ParsePosition pos = new ParsePosition(-1); + + char c; + do + { + c = next(pos, charArray); + switch (state) + { + case BEFORETOKEN -> + { + assert currentToken.isEmpty(); + if (Character.isWhitespace(c)) + continue; + if (c == '"') + state = STATE.INQUOTEDTOKEN; + else if (c == ',' || c == '\0') + result.add(currentToken.toString()); + else + { + currentToken.append(c); + state = STATE.INTOKEN; + } + } + case AFTERTOKEN -> + { + assert currentToken.length() == 0; + if (Character.isWhitespace(c)) + continue; + if (c == ',' || c == '\0') + state = STATE.BEFORETOKEN; + else + throw new ConversionException("Badly formatted list of strings"); + } + case INTOKEN -> + { + if (c == ',' || c == '\0') + { + result.add(currentToken.toString().trim()); + currentToken.setLength(0); + state = STATE.BEFORETOKEN; + } + else + { + currentToken.append(c); + } + } + case INQUOTEDTOKEN -> + { + if (c == '\0') + throw new ConversionException("Unterminated quoted string"); + else if (c != '"') + { + currentToken.append(c); + } + else if (peek(pos, charArray) == '"') + { + next(pos, charArray); + currentToken.append('"'); + } + else + { + result.add(currentToken.toString()); + currentToken.setLength(0); + state = STATE.AFTERTOKEN; + } + } + } + } while (0 != c); + return result; + } + + // Google Sheets compatible version of joinValuesToString() + public static String joinValuesToStringForExport(@NotNull List values) + { + return values.stream() + .map(value -> null==value ? "" : shouldEscapeForExport(value) ? "\"" + Strings.CS.replace(value,"\"", "\"\"") + "\"": value) + .collect(Collectors.joining(", ")); + } + + private static boolean shouldEscapeForExport(@NotNull String value) + { + if (value.isEmpty()) + return true; + if (Character.isWhitespace(value.charAt(0)) || Character.isWhitespace(value.charAt(value.length()-1))) + return true; + return StringUtils.containsAny(value,",\""); + } + + /** * Issue 52925: App export to csv/tsv ignores filter with column containing double quote * Issue 52119: App issues with assay run properties with special characters @@ -2759,6 +2876,7 @@ public void testEncodeURIComponent() @Test public void testRobot() { + @SuppressWarnings("HttpUrlsUsage") List bots = Arrays.asList( "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html", "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)", @@ -2912,6 +3030,76 @@ public void testJoinAndSplit() } @Test + public void testGoogleSheetMultiValue() + { + var googleMultiChoice = """ + Option 1, Option 2, "A""quote", "B,comma", C\\backslash + """.trim(); + var expectedList = List.of("Option 1", "Option 2", "A\"quote", "B,comma", "C\\backslash"); + var list = splitStringToValuesForImport(googleMultiChoice); + assertEquals(expectedList.size(), list.size()); + for (int i=0 ; i null or List.of() + assertNull(splitStringToValuesForImport(null)); + // should "" -> List.of() or List.of("") + assertEquals(List.of(""), splitStringToValuesForImport("")); + assertEquals(List.of("",""), splitStringToValuesForImport(",")); + assertEquals(List.of("","",""), splitStringToValuesForImport(", ,")); + + List> quickTests = Arrays.asList( + List.of(""), + List.of("",""), + List.of("","",""), + List.of(" A", "B ", "C D"), + List.of(",A", "B,", "C,D"), + List.of("\tA", "B\t", "C\tD"), + List.of("\"A", "B\"", "C\"D") + ); + for (List test : quickTests) + assertEquals(test, splitStringToValuesForImport(joinValuesToStringForExport(test))); + } + + @Test + public void testGoogleSheetMultiValueBad() + { + try + { + splitStringToValuesForImport("\"A\"dreck,B,C"); // unescaped " + fail("Expected exception"); + } + catch (ConversionException e) + { + // expected + } + + try + { + splitStringToValuesForImport("A,\"B,C"); // unterminated " + fail("Expected exception"); + } + catch (ConversionException e) + { + // expected + } + } + + + @Test public void encodePath() { assertEquals("a/b/c", PageFlowUtil.encodePath("a/b/c")); diff --git a/core/src/org/labkey/core/view/TableViewFormTestCase.java b/core/src/org/labkey/core/view/TableViewFormTestCase.java index 3d8a96084c2..f4489f960d9 100644 --- a/core/src/org/labkey/core/view/TableViewFormTestCase.java +++ b/core/src/org/labkey/core/view/TableViewFormTestCase.java @@ -19,6 +19,8 @@ import org.junit.Assert; import org.junit.Test; import org.labkey.api.action.NullSafeBindException; +import org.labkey.api.action.SpringActionController; +import org.labkey.api.data.BeanViewForm; import org.labkey.api.data.Container; import org.labkey.api.data.TableViewForm; import org.labkey.api.data.TestSchema; @@ -27,11 +29,15 @@ import org.labkey.api.util.TestContext; import org.labkey.api.view.HttpView; import org.labkey.api.view.ViewContext; +import org.springframework.beans.MutablePropertyValues; import org.springframework.validation.BindException; import java.sql.SQLException; import java.sql.Timestamp; import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; public class TableViewFormTestCase extends Assert { @@ -39,33 +45,162 @@ public class TableViewFormTestCase extends Assert public void testBasic() { TestForm tf = new TestForm(); - tf.setViewContext(HttpView.currentContext()); TestContext ctx = TestContext.get(); Assert.assertEquals(ctx.getRequest().getUserPrincipal(), tf.getUser()); //Test date handling - tf.set("datetimeNotNull", "2004-06-20"); + tf.setValueToBind("datetimeNotNull", "2004-06-20"); Date dt = (Date) tf.getTypedValue("datetimeNotNull"); - Assert.assertTrue("Date get", dt.equals(new Timestamp(DateUtil.parseISODateTime("2004-06-20")))); + Assert.assertEquals("Date get", dt, new Timestamp(DateUtil.parseISODateTime("2004-06-20"))); //Should turn empty strings into nulls - tf.set("text", ""); + tf.setValueToBind("text", ""); Assert.assertNull("Turn string to null", tf.getTypedValue("text")); - tf.set("bitNull", "1"); + tf.setValueToBind("bitNull", "1"); Assert.assertTrue((Boolean) tf.getTypedValue("bitNull")); tf.setPkVal(20); - Assert.assertEquals("20", tf.get("rowId")); + Assert.assertEquals("20", tf.getAsString("rowId")); Assert.assertEquals(20, tf.getTypedValue("rowId")); } + @SuppressWarnings("unused") + public static class BindBean + { + private String[] strArray; + private Boolean boolValue; + + public String[] getStrArray() + { + return strArray; + } + + public void setStrArray(String[] strArray) + { + this.strArray = strArray; + } + + public Boolean getBoolValue() + { + return boolValue; + } + + public void setBoolValue(Boolean boolValue) + { + this.boolValue = boolValue; + } + } + + @Test + public void testArray() + { + // actually using a BeanVieForm because SQL Server + BeanViewForm form; + MutablePropertyValues mpv; + Map typed; + String[] strArray; + + // test comma parsing + form = new BeanViewForm<>(BindBean.class, null); + mpv = new MutablePropertyValues(); + mpv.add("strArray", "Option 1, Option 2"); + form.bindParameters(mpv); + typed = form.getTypedValues(); + assertEquals(1, typed.size()); + assertTrue(typed.get("strArray") instanceof String[]); + strArray = (String[]) typed.get("strArray"); + assertEquals(2, strArray.length); + assertEquals("Option 1", strArray[0]); + assertEquals("Option 2", strArray[1]); + + // test disambiguate one select + // this is explicitly an array of size 1, so no parsing + form = new BeanViewForm<>(BindBean.class, null); + mpv = new MutablePropertyValues(); + mpv.add("strArray[]", "Option 1, Option 2"); + form.bindParameters(mpv); + typed = form.getTypedValues(); + assertEquals(1, typed.size()); + assertTrue(typed.get("strArray") instanceof String[]); + strArray = (String[]) typed.get("strArray"); + assertEquals(1, strArray.length); + assertEquals("Option 1, Option 2", strArray[0]); + } + + + @Test + public void testBoolean() + { + TestForm form; + MutablePropertyValues mpv; + Map typed; + + form = new TestForm(); + mpv = new MutablePropertyValues(); + mpv.add("other", "1"); + form.bindParameters(mpv); + typed = form.getTypedValues(); + assertFalse(typed.containsKey("bitNull")); + + form = new TestForm(); + mpv = new MutablePropertyValues(); + mpv.add("bitNull", "1"); + form.bindParameters(mpv); + typed = form.getTypedValues(); + assertEquals(1, typed.size()); + assertTrue(typed.get("bitNull") instanceof Boolean); + assertTrue((Boolean) typed.get("bitNull")); + + form = new TestForm(); + mpv = new MutablePropertyValues(); + mpv.add("bitNull", "0"); + form.bindParameters(mpv); + typed = form.getTypedValues(); + assertEquals(1, typed.size()); + assertTrue(typed.get("bitNull") instanceof Boolean); + assertFalse((Boolean) typed.get("bitNull")); + + form = new TestForm(); + mpv = new MutablePropertyValues(); + mpv.add(SpringActionController.FIELD_MARKER+"bitNull", "1"); + form.bindParameters(mpv); + typed = form.getTypedValues(); + assertEquals(1, typed.size()); + assertTrue(typed.get("bitNull") instanceof Boolean); + assertFalse((Boolean) typed.get("bitNull")); + + form = new TestForm(); + mpv = new MutablePropertyValues(); + mpv.add(SpringActionController.FIELD_MARKER+"bitNull", "1"); + mpv.add("bitNull", "1"); + form.bindParameters(mpv); + typed = form.getTypedValues(); + assertEquals(1, typed.size()); + assertTrue(typed.get("bitNull") instanceof Boolean); + assertTrue((Boolean) typed.get("bitNull")); + } + + + @Test public void testErrorHandling() { TestForm tf = new TestForm(); - tf.setViewContext(HttpView.currentContext()); //Should be invalid because of null fields. //BUG: Not differentiating between insert & update cases. @@ -76,10 +211,10 @@ public void testErrorHandling() //Assert.assertEquals("3 Non-null fields", errors.size(), 3); //Non-nullable fields are named NotNull - tf.set("datetimeNotNull", "2004-06-20"); - tf.set("bitNotNull", "1"); - tf.set("intNotNull", "20"); - tf.set("datetimeNull", "garbage"); + tf.setValueToBind("datetimeNotNull", "2004-06-20"); + tf.setValueToBind("bitNotNull", "1"); + tf.setValueToBind("intNotNull", "20"); + tf.setValueToBind("datetimeNull", "garbage"); BindException errors = new NullSafeBindException(tf, "form"); tf.validateBind(errors); @@ -97,17 +232,15 @@ public void testDbOperations() throws SQLException { Container test = JunitUtil.getTestContainer(); - ViewContext ctx = new ViewContext(HttpView.currentContext()); + ViewContext ctx = new ViewContext(Objects.requireNonNull(HttpView.currentContext())); ctx.setContainer(test); TestForm tf = new TestForm(); - tf.setViewContext(ctx); - - tf.set("datetimeNotNull", "2004-06-20"); - tf.set("bitNotNull", "1"); - tf.set("intNotNull", "20"); - tf.set("datetimeNull", "2004-06-20"); - tf.set("text", "First test record"); + tf.setValueToBind("datetimeNotNull", "2004-06-20"); + tf.setValueToBind("bitNotNull", "1"); + tf.setValueToBind("intNotNull", "20"); + tf.setValueToBind("datetimeNull", "2004-06-20"); + tf.setValueToBind("text", "First test record"); tf.doInsert(); Assert.assertNotNull(tf.getPkVal()); @@ -116,9 +249,12 @@ public void testDbOperations() throws SQLException Date createdDate = (Date) tf.getTypedValue("created"); //Make sure date->string->date comes out right... - tf.set("datetimeNotNull", tf.get("created")); - tf.set("text", "Second test record"); - tf.getStrings().remove("rowId"); + Map copy = new HashMap<>(tf.getValuesToBind()); + copy.remove("rowId"); + copy.put("datetimeNotNull", tf.getAsString("created")); + copy.put("text", "Second test record"); + tf = new TestForm(); + tf.setValuesToBind(copy); tf.doInsert(); Assert.assertEquals("Date time roundtrip: ", createdDate.getTime(), ((Date) tf.getTypedValue("datetimeNotNull")).getTime()); tf.doDelete(); @@ -129,7 +265,7 @@ public void testDbOperations() throws SQLException tf.doDelete(); tf.forceReselect(); - Assert.assertTrue("deleted", 1 == tf.getTypedValues().size()); + Assert.assertEquals("deleted", 1, tf.getTypedValues().size()); } public static class TestForm extends TableViewForm @@ -137,6 +273,7 @@ public static class TestForm extends TableViewForm public TestForm() { super(TestSchema.getInstance().getTableInfoTestTable()); + setViewContext(Objects.requireNonNull(HttpView.currentContext())); } } } diff --git a/issues/src/org/labkey/issue/IssuesController.java b/issues/src/org/labkey/issue/IssuesController.java index 5e652fe5c0b..1eade2b3251 100644 --- a/issues/src/org/labkey/issue/IssuesController.java +++ b/issues/src/org/labkey/issue/IssuesController.java @@ -63,7 +63,6 @@ import org.labkey.api.data.NormalContainerType; import org.labkey.api.data.ObjectFactory; import org.labkey.api.data.PHI; -import org.labkey.api.data.PropertyStorageSpec; import org.labkey.api.data.RenderContext; import org.labkey.api.data.Results; import org.labkey.api.data.SimpleFilter; @@ -748,13 +747,13 @@ public List getIssueForms() IssuesForm form = new IssuesForm(); form.setUser(getViewContext().getUser()); form.setContainer(getViewContext().getContainer()); - Map stringMap = new CaseInsensitiveHashMap<>(); + Map stringMap = new CaseInsensitiveHashMap<>(); for (String prop : rec.keySet()) { Object value = rec.get(prop); stringMap.put(prop, JSONObject.NULL.equals(value) ? null : value.toString()); } - form.setStrings(stringMap); + form.setValuesToBind(stringMap); _issueForms.add(form); } } @@ -783,8 +782,8 @@ public class IssuesAction extends AbstractIssueApiAction @Override Issue.action getAction(IssuesForm form) { - if (form.getStrings().containsKey("action")) - return Issue.action.valueOf(form.getStrings().get("action")); + if (form.getValuesToBind().containsKey("action")) + return Issue.action.valueOf(form.getAsString("action")); return null; } } @@ -816,15 +815,15 @@ public void validateForm(IssuesApiForm form, Errors errors) IssueObject prevIssue = action != Issue.action.insert ? IssueManager.getIssue(getContainer(), getUser(), issuesForm.getIssueId()) : null; Map prevIssueProps = prevIssue == null ? Collections.emptyMap() : prevIssue.getProperties(); - Map stringMap = new CaseInsensitiveHashMap<>(issuesForm.getStrings()); + Map stringMap = new CaseInsensitiveHashMap<>(issuesForm.getValuesToBind()); for (DomainProperty prop : issueListDef.getDomain(getUser()).getProperties()) { if (!IssueDefDomainKind.RESOLUTION_LOOKUP.equalsIgnoreCase(prop.getName())) stringMap.computeIfAbsent(prop.getName(), (propName) -> Objects.toString(prevIssueProps.get(propName), null)); } // Be sure that the posted values take precedence, even if they're null - stringMap.putAll(issuesForm.getStrings()); - issuesForm.setStrings(stringMap); + stringMap.putAll(issuesForm.getValuesToBind()); + issuesForm.setValuesToBind(stringMap); } IssueObject issue = issuesForm.getBean(); @@ -864,7 +863,7 @@ public ApiResponse execute(IssuesApiForm form, BindException errors) throws Exce setTypedProperties(issue, issuesForm, issueListDef.getName()); // handle attachments, the attachment value is a | delimited array of file names - String attachments = issuesForm.get("attachment"); + String attachments = issuesForm.getAsString("attachment"); List attachmentFiles = new ArrayList<>(); if (!StringUtils.isBlank(attachments)) { @@ -1370,9 +1369,9 @@ public ModelAndView getView(IssuesController.IssuesForm form, boolean reshow, Bi if (_issue.getResolution() == null || _issue.getResolution().isEmpty()) { - if (form.get("resolution") != null) + if (form.getAsString("resolution") != null) { - _issue.setResolution(form.get("resolution")); + _issue.setResolution(form.getAsString("resolution")); } } beforeReshow(reshow, form, _issue, getIssueListDef()); @@ -2218,9 +2217,9 @@ public IssuesForm() setValidateRequired(false); } - private static Map extraProps() + private static Map> extraProps() { - Map map = new LinkedHashMap<>(); + Map> map = new LinkedHashMap<>(); map.put("action", String.class); map.put("callbackURL", String.class); return map; @@ -2228,46 +2227,46 @@ private static Map extraProps() public Issue.action getAction() { - if (getStrings().containsKey("action")) - return Issue.action.valueOf(getStrings().get("action")); + if (getValuesToBind().containsKey("action")) + return Issue.action.valueOf(getAsString("action")); throw new NotFoundException("No action specified"); } public String getComment() { - return _stringValues.get("comment"); + return getAsString("comment"); } public String getNotifyList() { - return _stringValues.get("notifyList"); + return getAsString("notifyList"); } // XXX: change return value to typed ReturnURLString public String getCallbackURL() { - return _stringValues.get("callbackURL"); + return getAsString("callbackURL"); } public String getBody() { - return _stringValues.get("body"); + return getAsString("body"); } public String getPriority() { - return _stringValues.get("priority"); + return getAsString("priority"); } private String getIssueDefName() { - return _stringValues.get(IssuesListView.ISSUE_LIST_DEF_NAME); + return getAsString(IssuesListView.ISSUE_LIST_DEF_NAME); } private String getIssueDefId() { - return _stringValues.get(IssuesListView.ISSUE_LIST_DEF_ID); + return getAsString(IssuesListView.ISSUE_LIST_DEF_ID); } // Make this method public @@ -2284,7 +2283,7 @@ public void setTable(@NotNull TableInfo table) */ public boolean getSkipPost() { - return BooleanUtils.toBoolean(_stringValues.get("skipPost")); + return BooleanUtils.toBoolean(getAsString("skipPost")); } public ActionURL getForwardURL() @@ -2304,12 +2303,12 @@ public ActionURL getForwardURL() public int getIssueId() { - return NumberUtils.toInt(_stringValues.get("issueId")); + return NumberUtils.toInt(getAsString("issueId")); } public boolean isDirty() { - return BooleanUtils.toBoolean(_stringValues.get("dirty")); + return BooleanUtils.toBoolean(getAsString("dirty")); } } diff --git a/study/src/org/labkey/study/controllers/CohortController.java b/study/src/org/labkey/study/controllers/CohortController.java index 22da7a171f1..fc2463fa34d 100644 --- a/study/src/org/labkey/study/controllers/CohortController.java +++ b/study/src/org/labkey/study/controllers/CohortController.java @@ -341,7 +341,7 @@ public ModelAndView getView(EditCohortForm form, boolean reshow, BindException e { view = new InsertView(updateForm, errors); // by default, cohorts are enrolled - updateForm.set("enrolled", true); + updateForm.setValueToBind("enrolled", true); } else { diff --git a/study/src/org/labkey/study/controllers/StudyController.java b/study/src/org/labkey/study/controllers/StudyController.java index 4cd599b94ee..55aa0fee64b 100644 --- a/study/src/org/labkey/study/controllers/StudyController.java +++ b/study/src/org/labkey/study/controllers/StudyController.java @@ -1716,7 +1716,7 @@ public void validateForm(TableViewForm form, Errors errors) { // Issue 47444 and Issue 47881: Validate that subject noun singular doesn't match the name of an existing // study table or dataset - String subjectNounSingular = form.get("SubjectNounSingular"); + String subjectNounSingular = form.getAsString("SubjectNounSingular"); if (null != subjectNounSingular) { String message = StudyService.get().getSubjectNounSingularValidationErrorMessage(getContainer(), subjectNounSingular); @@ -1728,7 +1728,7 @@ public void validateForm(TableViewForm form, Errors errors) // Skip validation if Spring binding already has an error for subject noun plural if (errors.getFieldError("SubjectNounPlural") == null) { - String subjectNounPlural = form.get("SubjectNounPlural"); + String subjectNounPlural = form.getAsString("SubjectNounPlural"); if (null != subjectNounPlural) { String message = StudyService.get().getSubjectNounPluralValidationErrorMessage(getContainer(), subjectNounPlural); @@ -1741,7 +1741,7 @@ public void validateForm(TableViewForm form, Errors errors) if (errors.getFieldError("SubjectColumnName") == null) { // Issue 43898: Validate that the subject column name is not a user-defined field in one of the datasets - String subjectColName = form.get("SubjectColumnName"); + String subjectColName = form.getAsString("SubjectColumnName"); if (null != subjectColName) { String message = StudyService.get().getSubjectColumnNameValidationErrorMessage(getContainer(), subjectColName); diff --git a/study/src/org/labkey/study/controllers/StudyPropertiesController.java b/study/src/org/labkey/study/controllers/StudyPropertiesController.java index baf075e77c8..c66f8f12c03 100644 --- a/study/src/org/labkey/study/controllers/StudyPropertiesController.java +++ b/study/src/org/labkey/study/controllers/StudyPropertiesController.java @@ -72,7 +72,7 @@ public ModelAndView getView(StudyProperties studyPropertiesForm, boolean reshow, // In order to pull the data out for an edit, we need to explicitly add the container id to the parameters // that the query update form will use - updateForm.set("container", getContainer().getId()); + updateForm.setValueToBind("container", getContainer().getId()); UpdateView view = new UpdateView(updateForm, errors); DataRegion dataRegion = view.getDataRegion();