From d8f5b9c118c3e946148137d919bc779e0ea8dbfa Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 11 Mar 2026 21:51:52 -0700 Subject: [PATCH 1/4] Bug bash: round 1 --- .../org/labkey/api/audit/AuditHandler.java | 6 +- .../labkey/api/data/NameGeneratorState.java | 5 + .../org/labkey/api/exp/ObjectProperty.java | 5 + api/src/org/labkey/api/exp/PropertyType.java | 2432 ++++++++--------- .../labkey/api/exp/property/DomainUtil.java | 15 +- api/src/org/labkey/api/reader/DataLoader.java | 7 + 6 files changed, 1250 insertions(+), 1220 deletions(-) diff --git a/api/src/org/labkey/api/audit/AuditHandler.java b/api/src/org/labkey/api/audit/AuditHandler.java index 2c765844544..0212dce7002 100644 --- a/api/src/org/labkey/api/audit/AuditHandler.java +++ b/api/src/org/labkey/api/audit/AuditHandler.java @@ -91,8 +91,6 @@ static Pair, Map> getOldAndNewRecordForMerge if (col != null && (col.isMultiValued() || col.getFk() instanceof MultiValuedForeignKey)) isMultiValued = true; - boolean isMultiChoice = col != null && col.getPropertyType() == PropertyType.MULTI_CHOICE; - String nameFromAlias = key; if (null != col) nameFromAlias = col.getName(); @@ -104,9 +102,13 @@ static Pair, Map> getOldAndNewRecordForMerge { if (aliasColumn.getFk() != null && (aliasColumn.isMultiValued() || aliasColumn.getFk() instanceof MultiValuedForeignKey)) isMultiValued = true; + col = aliasColumn; // GitHub Issue 913: Updating a sample details page shows an update to the MVTC field nameFromAlias = aliasColumn.getName(); } } + + boolean isMultiChoice = col != null && col.getPropertyType() == PropertyType.MULTI_CHOICE; + String lcName = nameFromAlias.toLowerCase(); // Preserve casing of inputs so we can show the names properly boolean isExpInput = false; // TODO: extract lineage handling out of this generic method diff --git a/api/src/org/labkey/api/data/NameGeneratorState.java b/api/src/org/labkey/api/data/NameGeneratorState.java index db50e3715af..7f69e445fcc 100644 --- a/api/src/org/labkey/api/data/NameGeneratorState.java +++ b/api/src/org/labkey/api/data/NameGeneratorState.java @@ -353,6 +353,11 @@ else if (parentObject instanceof ExpData data) { rawObj = (Double) rawObj < 1.0 ? Boolean.FALSE : Boolean.TRUE; } + else if (PropertyType.MULTI_CHOICE.equals(pt) && rawObj == null) + { + // GitHub Issue 914: Using MVTC field in Ancestry Naming Pattern is always blank + rawObj = prop.getArrayValue(); + } properties.put(prop.getName(), pt.convert(rawObj)); } diff --git a/api/src/org/labkey/api/exp/ObjectProperty.java b/api/src/org/labkey/api/exp/ObjectProperty.java index 72a61647493..5c3b0c3d4be 100644 --- a/api/src/org/labkey/api/exp/ObjectProperty.java +++ b/api/src/org/labkey/api/exp/ObjectProperty.java @@ -169,6 +169,11 @@ public PropertyType getPropertyType() return PropertyType.getFromURI(getConceptURI(), getRangeURI()); } + public MultiChoice.Array getArrayValue() + { + return arrayValue; + } + public Container getContainer() { return ContainerManager.getForId(containerId); diff --git a/api/src/org/labkey/api/exp/PropertyType.java b/api/src/org/labkey/api/exp/PropertyType.java index 8d42cc373a2..b32731c6ef2 100644 --- a/api/src/org/labkey/api/exp/PropertyType.java +++ b/api/src/org/labkey/api/exp/PropertyType.java @@ -1,1216 +1,1216 @@ -/* - * 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.exp; - -import org.apache.commons.beanutils.ConversionException; -import org.apache.commons.beanutils.ConvertUtils; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.CellType; -import org.fhcrc.cpas.exp.xml.SimpleTypeNames; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.attachments.AttachmentFile; -import org.labkey.api.collections.CaseInsensitiveHashMap; -import org.labkey.api.data.JdbcType; -import org.labkey.api.data.MultiChoice; -import org.labkey.api.data.NameGenerator; -import org.labkey.api.data.SimpleConvert; -import org.labkey.api.exp.OntologyManager.PropertyRow; -import org.labkey.api.reader.ExcelFactory; -import org.labkey.api.util.DateUtil; -import org.labkey.vfs.FileLike; - -import java.io.File; -import java.math.BigDecimal; -import java.nio.ByteBuffer; -import java.sql.Time; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TimeZone; - -import static org.labkey.api.util.IntegerUtils.asIntegerElseNull; -import static org.labkey.api.util.IntegerUtils.asLongElseNull; - -/** - * TODO: Add more types? Entity, Lsid, User, ... - */ -public enum PropertyType implements SimpleConvert -{ - BOOLEAN("http://www.w3.org/2001/XMLSchema#boolean", "Boolean", 'f', JdbcType.BOOLEAN, 10, null, CellType.BOOLEAN, Boolean.class, Boolean.TYPE) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getBooleanCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - Boolean boolValue = null; - if (value instanceof Boolean) - boolValue = (Boolean)value; - else if (null != value && !"".equals(value)) - boolValue = (Boolean) ConvertUtils.convert(value.toString(), Boolean.class); - return boolValue; - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.BOOLEAN; - } - - @Override - protected void init(PropertyRow row, Object value) - { - Boolean b = (Boolean)value; - row.floatValue = b == Boolean.TRUE ? 1.0 : 0.0; - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - Boolean boolValue = null; - if (value instanceof Boolean) - boolValue = (Boolean)value; - else if (null != value) - boolValue = (Boolean) ConvertUtils.convert(value.toString(), Boolean.class); - property.floatValue = boolValue == null ? null : boolValue == Boolean.TRUE ? 1.0 : 0.0; - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.floatValue == null ? null : property.floatValue.intValue() != 0 ? Boolean.TRUE : Boolean.FALSE; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return Boolean.TRUE; - } - }, - STRING("http://www.w3.org/2001/XMLSchema#string", "String", 's', JdbcType.VARCHAR, 4000, "text", CellType.STRING, String.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getStringCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return value; - if (value instanceof CharSequence cs) - return cs.isEmpty() ? null : cs.toString(); - return ConvertUtils.convert(value, String.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.STRING; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.stringValue = (String)value; - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - property.stringValue = value == null ? null : value.toString(); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.getStringValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return prefix + "Value"; - } - }, - // NOT an XMLSchema type uri??? - MULTI_LINE("http://www.w3.org/2001/XMLSchema#multiLine", "MultiLine", 's', JdbcType.VARCHAR, 4000, "textarea", CellType.STRING, String.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getStringCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - return STRING.convert(value); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.STRING; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.stringValue = (String)value; - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - property.stringValue = value == null ? null : value.toString(); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.getStringValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return prefix + "Value"; - } - }, - MULTI_CHOICE("http://cpas.fhcrc.org/exp/xml#multiChoice", "MultiChoice", '?' /* unsupported in exp.PropertyValues */, JdbcType.ARRAY, 0, "textarea", CellType.STRING, List.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return ConvertUtils.convert(cell.getStringCellValue(), MultiChoice.Array.class); - } - - @Override - public Object convert(Object value) throws ConversionException - { - return MultiChoice.Converter.getInstance().convert(MultiChoice.Array.class, value); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.STRING; - } - - @Override - protected void init(PropertyRow row, Object value) - { - throw new UnsupportedOperationException("TODO MultiChoice"); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - property.arrayValue = MultiChoice.Converter.getInstance().convert(MultiChoice.Array.class, value); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.arrayValue; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return "Option 1, Option 2"; - } - }, - RESOURCE("http://www.w3.org/2000/01/rdf-schema#Resource", "PropertyURI", 's', JdbcType.VARCHAR, 4000, null, CellType.STRING, Identifiable.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getStringCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof Identifiable) - return ((Identifiable) value).getLSID(); - else - return value.toString(); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.STRING; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.stringValue = (String)value; - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof Identifiable) - { - property.stringValue = ((Identifiable) value).getLSID(); - property.objectValue = (Identifiable) value; - } - else if (null != value) - property.stringValue = value.toString(); - } - - @Override - protected Object getValue(ObjectProperty property) - { - if (null != property.objectValue) - return property.objectValue; - else - return property.getStringValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return prefix + "Value"; - } - }, - INTEGER("http://www.w3.org/2001/XMLSchema#int", "Integer", 'f', JdbcType.INTEGER, 10, null, CellType.NUMERIC, Integer.class, Integer.TYPE, Long.class, Long.TYPE) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return (int)cell.getNumericCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (asIntegerElseNull(value) instanceof Integer i) - return i; - else - return ConvertUtils.convert(value.toString(), Integer.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.INTEGER; - } - - @Override - protected void init(PropertyRow row, Object value) - { - Number n = (Number) value; - if (null != n) - row.floatValue = n.doubleValue(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (null == value) - property.floatValue = null; - else if (asIntegerElseNull(value) instanceof Integer i) - property.floatValue = i.doubleValue(); - else - property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.floatValue == null ? null : property.floatValue.intValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return Integer.valueOf(3); - } - }, - BIGINT("http://www.w3.org/2001/XMLSchema#long", "Long", 'f', JdbcType.BIGINT, 10, null, CellType.NUMERIC, Long.class, Long.TYPE) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return (int)cell.getNumericCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (asLongElseNull(value) instanceof Long l) - return l; - else - return ConvertUtils.convert(value.toString(), Long.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - throw new UnsupportedOperationException(); - } - - @Override - protected void init(PropertyRow row, Object value) - { - Number n = (Number) value; - if (null != n) - row.floatValue = n.doubleValue(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (null == value) - property.floatValue = null; - else if (asLongElseNull(value) instanceof Long l) - property.floatValue = l.doubleValue(); - else - property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.floatValue == null ? null : property.floatValue.longValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return Integer.valueOf(3); - } - }, - // NOT an XMLSchema type uri??? - BINARY("http://www.w3.org/2001/XMLSchema#binary", "Binary", 'f', JdbcType.BINARY, 10, null, CellType.NUMERIC, ByteBuffer.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return (int)cell.getNumericCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof ByteBuffer) - return value; - else - return ConvertUtils.convert(value.toString(), ByteBuffer.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - throw new UnsupportedOperationException(); - } - - @Override - protected void init(PropertyRow row, Object value) - { - throw new UnsupportedOperationException(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (null != value) - property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - throw new UnsupportedOperationException(); - } - }, - /** Stored as a path to a file on the server's file system */ - FILE_LINK("http://cpas.fhcrc.org/exp/xml#fileLink", "FileLink", 's', JdbcType.VARCHAR, 400, "file", CellType.STRING, File.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getStringCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof File) - return ((File) value).getPath(); - else - return String.valueOf(value); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.FILE_LINK; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.stringValue = (String)value; - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof File f) - property.stringValue = f.getPath(); - else if (value instanceof FileLike fl) - property.stringValue = fl.toNioPathForRead().toString(); - else - property.stringValue = value == null ? null : value.toString(); - } - - @Override - protected Object getValue(ObjectProperty property) - { - String value = property.getStringValue(); - return value == null ? null : new File(value); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return prefix + "Value"; - } - }, - /** Stored in the database as a BLOB using AttachmentService */ - ATTACHMENT("http://www.labkey.org/exp/xml#attachment", "Attachment", 's', JdbcType.VARCHAR, 100, "file", CellType.STRING, File.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getStringCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof File) - return ((File) value).getPath(); - else - return String.valueOf(value); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - throw new UnsupportedOperationException(); - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.stringValue = (String)value; - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof AttachmentFile) - { - property.stringValue = ((AttachmentFile)value).getFilename(); - } - else - property.stringValue = value == null ? null : value.toString(); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.getStringValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return prefix + "Value"; - } - }, - DATE_TIME("http://www.w3.org/2001/XMLSchema#dateTime", "DateTime", 'd', JdbcType.TIMESTAMP, 100, null, CellType.NUMERIC, Date.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - Date date = cell.getDateCellValue(); - if (date != null) - { - DateFormat format = new SimpleDateFormat("MM/dd/yyyy GG HH:mm:ss.SSS"); - format.setTimeZone(TimeZone.getDefault()); - String s = format.format(date); - try - { - date = format.parse(s); - } - catch (ParseException e) - { - throw new ConversionException(e); - } - } - return date; - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof Date) - return value; - else - { - String strVal = value.toString(); - if (DateUtil.isSignedDuration(strVal)) - strVal = JdbcType.TIMESTAMP.convert(value).toString(); - return ConvertUtils.convert(strVal, java.sql.Timestamp.class); - } - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.DATE_TIME; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.dateTimeValue = new java.sql.Time(((java.util.Date)value).getTime()); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof Date) - property.dateTimeValue = (Date) value; - else if (null != value) - property.dateTimeValue = (Date) ConvertUtils.convert(value.toString(), Date.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.dateTimeValue; - } - - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return NameGenerator.PREVIEW_DATETIME_VALUE; - } - }, - DATE("http://www.w3.org/2001/XMLSchema#date", "Date", 'd', JdbcType.DATE, 100, null, CellType.NUMERIC, Date.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return DateUtil.getDateOnly((Date)DATE_TIME.convertExcelValue(cell)); - } - - @Override - public Object convert(Object value) throws ConversionException - { - return DateUtil.getDateOnly((Date)DATE_TIME.convert(value)); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.DATE_TIME; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.dateTimeValue = new java.sql.Date(((java.util.Date)value).getTime()); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof Date) - property.dateTimeValue = (Date) value; - else if (null != value) - property.dateTimeValue = (Date) ConvertUtils.convert(value.toString(), Date.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.dateTimeValue; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return NameGenerator.PREVIEW_DATE_VALUE; - } - }, - TIME("http://www.w3.org/2001/XMLSchema#time", "Time", 'd', JdbcType.TIME, 100, null, CellType.NUMERIC, java.sql.Time.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return DateUtil.getTimeOnly((Date)DATE_TIME.convertExcelValue(cell)); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - - if (value instanceof Time) - return value; - - if (value instanceof Date) - return DateUtil.getTimeOnly((Date) value); - - try - { - return ConvertUtils.convert(value, Time.class); - } - catch (Exception ignore) - { - } - - return DateUtil.getTimeOnly((Date)DATE_TIME.convert(value)); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.DATE_TIME; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.dateTimeValue = new java.sql.Time(((java.util.Date)value).getTime()); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof Time) - property.dateTimeValue = (Time) value; - else if (null != value) - property.dateTimeValue = (Time) ConvertUtils.convert(value.toString(), Time.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.dateTimeValue; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return NameGenerator.PREVIEW_TIME_VALUE; - } - }, - DOUBLE("http://www.w3.org/2001/XMLSchema#double", "Double", 'f', JdbcType.DOUBLE, 20, null, CellType.NUMERIC, Double.class, Double.TYPE, Float.class, Float.TYPE) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getNumericCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof Double) - return value; - else - return ConvertUtils.convert(String.valueOf(value), Double.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.DOUBLE; - } - - @Override - protected void init(PropertyRow row, Object value) - { - Number n = (Number) value; - if (null != n) - row.floatValue = n.doubleValue(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof Double) - property.floatValue = (Double) value; - else if (null != value) - property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.floatValue; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return 12.34; - } - }, - FLOAT("http://www.w3.org/2001/XMLSchema#float", "Float", 'f', JdbcType.REAL, 20, null, CellType.NUMERIC, Float.class, Float.TYPE) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getNumericCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof Float) - return value; - else - return ConvertUtils.convert(String.valueOf(value), Float.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - throw new UnsupportedOperationException(); - } - - @Override - protected void init(PropertyRow row, Object value) - { - Number n = (Number) value; - if (null != n) - row.floatValue = n.doubleValue(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof Double) - property.floatValue = (Double) value; - else if (null != value) - property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.floatValue; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return 12.34; - } - }, - DECIMAL("http://www.w3.org/2001/XMLSchema#decimal", "Decimal", 'f', JdbcType.DECIMAL, 20, null, CellType.NUMERIC, BigDecimal.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getNumericCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof BigDecimal) - return value; - else - return ConvertUtils.convert(String.valueOf(value), BigDecimal.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - throw new UnsupportedOperationException(); - } - - @Override - protected void init(PropertyRow row, Object value) - { - Number n = (Number) value; - if (null != n) - row.floatValue = n.doubleValue(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (null != value) - property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.floatValue; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return 12.34; - } - }, - XML_TEXT("http://cpas.fhcrc.org/exp/xml#text-xml", "XmlText", 's', JdbcType.LONGVARCHAR, 4000, null, CellType.STRING, null) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getStringCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - return STRING.convert(value); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - throw new UnsupportedOperationException(); - } - - @Override - protected void init(PropertyRow row, Object value) - { - throw new UnsupportedOperationException(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - throw new UnsupportedOperationException(); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.getStringValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return prefix + "Value"; - } - }; - - private final String typeURI; - private final String xarName; - private final char storageType; - private final CellType excelCellType; - private final @NotNull JdbcType jdbcType; - private final int scale; - private final String inputType; - private final Class javaType; - private final Class[] additionalTypes; - - private static Map uriToProperty; - private static Map xarToProperty = null; - - PropertyType(String typeURI, - String xarName, - char storageType, - @NotNull JdbcType jdbcType, - int scale, - String inputType, - CellType excelCellType, - Class javaType, - Class... additionalTypes) - { - this.typeURI = typeURI; - this.xarName = xarName; - this.storageType = storageType; - this.jdbcType = jdbcType; - this.scale = scale; - this.inputType = inputType; - this.javaType = javaType; - this.excelCellType = excelCellType; - this.additionalTypes = additionalTypes; - } - - /** - * The returned Function<Object,Object> should throw ConversionException (undeclared RuntimeException) - */ - - public String getTypeUri() - { - return typeURI; - } - - public String getXmlName() - { - return xarName; - } - - public char getStorageType() - { - return storageType; - } - - @NotNull - public JdbcType getJdbcType() - { - return jdbcType; - } - - public int getScale() - { - return scale; - } - - @Nullable - public String getInputType() - { - return inputType; - } - - public Class getJavaType() - { - return javaType; - } - - public String getXarName() - { - return xarName; - } - - @NotNull - public static PropertyType getFromURI(String concept, String datatype) - { - return getFromURI(concept, datatype, RESOURCE); - } - - @Deprecated // Eliminate this along with PropertyRow? Or at least combine with setValue() below. - abstract protected void init(PropertyRow row, Object value); - abstract protected void setValue(ObjectProperty property, Object value); - abstract protected Object getValue(ObjectProperty property); - public Object getPreviewValue(@Nullable String prefix) - { - return getValue(null); - } - - static - { - Map m = new HashMap<>(); - - for (PropertyType t : values()) - { - String uri = t.getTypeUri(); - m.put(uri, t); - m.put(t.getXmlName(), t); - - if (uri.startsWith("http://www.w3.org/2001/XMLSchema#") || uri.startsWith("http://www.labkey.org/exp/xml#")) - { - String xsdName = uri.substring(uri.indexOf('#') + 1); - m.put("xsd:" + xsdName, t); - m.put(xsdName, t); - } - } - - uriToProperty = m; - } - - public static PropertyType getFromURI(@Nullable String concept, String datatype, PropertyType def) - { - PropertyType p = uriToProperty.get(concept); - - if (null == p) - { - p = uriToProperty.get(datatype); - if (null == p) - p = def; - } - - return p; - } - - @NotNull - public static PropertyType getFromXarName(String xarName) - { - return getFromXarName(xarName, RESOURCE); - } - - public static PropertyType getFromXarName(String xarName, PropertyType def) - { - if (null == xarToProperty) - { - Map m = new CaseInsensitiveHashMap<>(); - for (PropertyType t : values()) - { - m.put(t.getXmlName(), t); - } - xarToProperty = m; - } - - PropertyType p = xarToProperty.get(xarName); - - return null == p ? def : p; - } - - public static PropertyType getFromClass(Class clazz) - { - if (clazz == BigDecimal.class) - clazz = Double.class; - - for (PropertyType t : values()) - { - if (t.javaType == null) - continue; - if (t.javaType.isAssignableFrom(clazz)) - return t; - } - - // after trying the primary types, we then try any additional types: - for (PropertyType t : values()) - { - if (t.additionalTypes == null || t.additionalTypes.length == 0) - continue; - for (Class type : t.additionalTypes) - { - if (type.isAssignableFrom(clazz)) - return t; - } - } - return PropertyType.STRING; - } - - @NotNull - public static PropertyType getFromJdbcType(JdbcType jdbcType) - { - return Objects.requireNonNull(getFromJdbcType(jdbcType, true)); - } - - @Nullable - public static PropertyType getFromJdbcType(JdbcType jdbcType, boolean throwIfNotFound) - { - for (PropertyType t : values()) - { - if (t.jdbcType.equals(jdbcType)) - return t; - } - if (throwIfNotFound) - throw new IllegalArgumentException("No such JdbcType mapping: " + (null != jdbcType ? jdbcType.getClass().toString() : "null")); - else - return null; - } - - @Nullable - public static PropertyType getFromJdbcTypeName(String typeName) - { - for (PropertyType t : values()) - { - if (typeName.equalsIgnoreCase(t.jdbcType.name())) - return t; - } - return null; - } - - public abstract SimpleTypeNames.Enum getXmlBeanType(); - - protected abstract Object convertExcelValue(Cell cell) throws ConversionException; - - public static Object getFromExcelCell(Cell cell) throws ConversionException - { - if (ExcelFactory.isCellNumeric(cell)) - { - // Ugly, the POI implementation doesn't expose an explicit date type - if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) - return DATE_TIME.convertExcelValue(cell); - else - // special handling for the "number type": prefer double. - // Without this, we'd default to integer - return DOUBLE.convertExcelValue(cell); - } - - for (PropertyType t : values()) - { - if (t.excelCellType == cell.getCellType()) - return t.convertExcelValue(cell); - } - return ExcelFactory.getCellStringValue(cell); - } - - public String getValueTypeColumn() - { - switch (this.getStorageType()) - { - case 's': - return "stringValue"; - case 'd': - return "dateTimeValue"; - case 'f': - return "floatValue"; - default: - throw new IllegalArgumentException("Unknown property type: " + this); - } - } -} +/* + * 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.exp; + +import org.apache.commons.beanutils.ConversionException; +import org.apache.commons.beanutils.ConvertUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.fhcrc.cpas.exp.xml.SimpleTypeNames; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.labkey.api.attachments.AttachmentFile; +import org.labkey.api.collections.CaseInsensitiveHashMap; +import org.labkey.api.data.JdbcType; +import org.labkey.api.data.MultiChoice; +import org.labkey.api.data.NameGenerator; +import org.labkey.api.data.SimpleConvert; +import org.labkey.api.exp.OntologyManager.PropertyRow; +import org.labkey.api.reader.ExcelFactory; +import org.labkey.api.util.DateUtil; +import org.labkey.vfs.FileLike; + +import java.io.File; +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.sql.Time; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TimeZone; + +import static org.labkey.api.util.IntegerUtils.asIntegerElseNull; +import static org.labkey.api.util.IntegerUtils.asLongElseNull; + +/** + * TODO: Add more types? Entity, Lsid, User, ... + */ +public enum PropertyType implements SimpleConvert +{ + BOOLEAN("http://www.w3.org/2001/XMLSchema#boolean", "Boolean", 'f', JdbcType.BOOLEAN, 10, null, CellType.BOOLEAN, Boolean.class, Boolean.TYPE) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getBooleanCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + Boolean boolValue = null; + if (value instanceof Boolean) + boolValue = (Boolean)value; + else if (null != value && !"".equals(value)) + boolValue = (Boolean) ConvertUtils.convert(value.toString(), Boolean.class); + return boolValue; + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.BOOLEAN; + } + + @Override + protected void init(PropertyRow row, Object value) + { + Boolean b = (Boolean)value; + row.floatValue = b == Boolean.TRUE ? 1.0 : 0.0; + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + Boolean boolValue = null; + if (value instanceof Boolean) + boolValue = (Boolean)value; + else if (null != value) + boolValue = (Boolean) ConvertUtils.convert(value.toString(), Boolean.class); + property.floatValue = boolValue == null ? null : boolValue == Boolean.TRUE ? 1.0 : 0.0; + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.floatValue == null ? null : property.floatValue.intValue() != 0 ? Boolean.TRUE : Boolean.FALSE; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return Boolean.TRUE; + } + }, + STRING("http://www.w3.org/2001/XMLSchema#string", "String", 's', JdbcType.VARCHAR, 4000, "text", CellType.STRING, String.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getStringCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return value; + if (value instanceof CharSequence cs) + return cs.isEmpty() ? null : cs.toString(); + return ConvertUtils.convert(value, String.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.STRING; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.stringValue = (String)value; + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + property.stringValue = value == null ? null : value.toString(); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.getStringValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return prefix + "Value"; + } + }, + // NOT an XMLSchema type uri??? + MULTI_LINE("http://www.w3.org/2001/XMLSchema#multiLine", "MultiLine", 's', JdbcType.VARCHAR, 4000, "textarea", CellType.STRING, String.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getStringCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + return STRING.convert(value); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.STRING; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.stringValue = (String)value; + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + property.stringValue = value == null ? null : value.toString(); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.getStringValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return prefix + "Value"; + } + }, + MULTI_CHOICE("http://cpas.fhcrc.org/exp/xml#multiChoice", "MultiChoice", '?' /* unsupported in exp.PropertyValues */, JdbcType.ARRAY, 0, "textarea", CellType.STRING, MultiChoice.Array.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return ConvertUtils.convert(cell.getStringCellValue(), MultiChoice.Array.class); + } + + @Override + public Object convert(Object value) throws ConversionException + { + return MultiChoice.Converter.getInstance().convert(MultiChoice.Array.class, value); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.STRING; + } + + @Override + protected void init(PropertyRow row, Object value) + { + throw new UnsupportedOperationException("TODO MultiChoice"); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + property.arrayValue = MultiChoice.Converter.getInstance().convert(MultiChoice.Array.class, value); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.arrayValue; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return "Option 1, Option 2"; + } + }, + RESOURCE("http://www.w3.org/2000/01/rdf-schema#Resource", "PropertyURI", 's', JdbcType.VARCHAR, 4000, null, CellType.STRING, Identifiable.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getStringCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof Identifiable) + return ((Identifiable) value).getLSID(); + else + return value.toString(); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.STRING; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.stringValue = (String)value; + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof Identifiable) + { + property.stringValue = ((Identifiable) value).getLSID(); + property.objectValue = (Identifiable) value; + } + else if (null != value) + property.stringValue = value.toString(); + } + + @Override + protected Object getValue(ObjectProperty property) + { + if (null != property.objectValue) + return property.objectValue; + else + return property.getStringValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return prefix + "Value"; + } + }, + INTEGER("http://www.w3.org/2001/XMLSchema#int", "Integer", 'f', JdbcType.INTEGER, 10, null, CellType.NUMERIC, Integer.class, Integer.TYPE, Long.class, Long.TYPE) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return (int)cell.getNumericCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (asIntegerElseNull(value) instanceof Integer i) + return i; + else + return ConvertUtils.convert(value.toString(), Integer.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.INTEGER; + } + + @Override + protected void init(PropertyRow row, Object value) + { + Number n = (Number) value; + if (null != n) + row.floatValue = n.doubleValue(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (null == value) + property.floatValue = null; + else if (asIntegerElseNull(value) instanceof Integer i) + property.floatValue = i.doubleValue(); + else + property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.floatValue == null ? null : property.floatValue.intValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return Integer.valueOf(3); + } + }, + BIGINT("http://www.w3.org/2001/XMLSchema#long", "Long", 'f', JdbcType.BIGINT, 10, null, CellType.NUMERIC, Long.class, Long.TYPE) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return (int)cell.getNumericCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (asLongElseNull(value) instanceof Long l) + return l; + else + return ConvertUtils.convert(value.toString(), Long.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void init(PropertyRow row, Object value) + { + Number n = (Number) value; + if (null != n) + row.floatValue = n.doubleValue(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (null == value) + property.floatValue = null; + else if (asLongElseNull(value) instanceof Long l) + property.floatValue = l.doubleValue(); + else + property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.floatValue == null ? null : property.floatValue.longValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return Integer.valueOf(3); + } + }, + // NOT an XMLSchema type uri??? + BINARY("http://www.w3.org/2001/XMLSchema#binary", "Binary", 'f', JdbcType.BINARY, 10, null, CellType.NUMERIC, ByteBuffer.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return (int)cell.getNumericCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof ByteBuffer) + return value; + else + return ConvertUtils.convert(value.toString(), ByteBuffer.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void init(PropertyRow row, Object value) + { + throw new UnsupportedOperationException(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (null != value) + property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + throw new UnsupportedOperationException(); + } + }, + /** Stored as a path to a file on the server's file system */ + FILE_LINK("http://cpas.fhcrc.org/exp/xml#fileLink", "FileLink", 's', JdbcType.VARCHAR, 400, "file", CellType.STRING, File.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getStringCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof File) + return ((File) value).getPath(); + else + return String.valueOf(value); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.FILE_LINK; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.stringValue = (String)value; + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof File f) + property.stringValue = f.getPath(); + else if (value instanceof FileLike fl) + property.stringValue = fl.toNioPathForRead().toString(); + else + property.stringValue = value == null ? null : value.toString(); + } + + @Override + protected Object getValue(ObjectProperty property) + { + String value = property.getStringValue(); + return value == null ? null : new File(value); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return prefix + "Value"; + } + }, + /** Stored in the database as a BLOB using AttachmentService */ + ATTACHMENT("http://www.labkey.org/exp/xml#attachment", "Attachment", 's', JdbcType.VARCHAR, 100, "file", CellType.STRING, File.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getStringCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof File) + return ((File) value).getPath(); + else + return String.valueOf(value); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.stringValue = (String)value; + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof AttachmentFile) + { + property.stringValue = ((AttachmentFile)value).getFilename(); + } + else + property.stringValue = value == null ? null : value.toString(); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.getStringValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return prefix + "Value"; + } + }, + DATE_TIME("http://www.w3.org/2001/XMLSchema#dateTime", "DateTime", 'd', JdbcType.TIMESTAMP, 100, null, CellType.NUMERIC, Date.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + Date date = cell.getDateCellValue(); + if (date != null) + { + DateFormat format = new SimpleDateFormat("MM/dd/yyyy GG HH:mm:ss.SSS"); + format.setTimeZone(TimeZone.getDefault()); + String s = format.format(date); + try + { + date = format.parse(s); + } + catch (ParseException e) + { + throw new ConversionException(e); + } + } + return date; + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof Date) + return value; + else + { + String strVal = value.toString(); + if (DateUtil.isSignedDuration(strVal)) + strVal = JdbcType.TIMESTAMP.convert(value).toString(); + return ConvertUtils.convert(strVal, java.sql.Timestamp.class); + } + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.DATE_TIME; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.dateTimeValue = new java.sql.Time(((java.util.Date)value).getTime()); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof Date) + property.dateTimeValue = (Date) value; + else if (null != value) + property.dateTimeValue = (Date) ConvertUtils.convert(value.toString(), Date.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.dateTimeValue; + } + + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return NameGenerator.PREVIEW_DATETIME_VALUE; + } + }, + DATE("http://www.w3.org/2001/XMLSchema#date", "Date", 'd', JdbcType.DATE, 100, null, CellType.NUMERIC, Date.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return DateUtil.getDateOnly((Date)DATE_TIME.convertExcelValue(cell)); + } + + @Override + public Object convert(Object value) throws ConversionException + { + return DateUtil.getDateOnly((Date)DATE_TIME.convert(value)); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.DATE_TIME; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.dateTimeValue = new java.sql.Date(((java.util.Date)value).getTime()); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof Date) + property.dateTimeValue = (Date) value; + else if (null != value) + property.dateTimeValue = (Date) ConvertUtils.convert(value.toString(), Date.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.dateTimeValue; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return NameGenerator.PREVIEW_DATE_VALUE; + } + }, + TIME("http://www.w3.org/2001/XMLSchema#time", "Time", 'd', JdbcType.TIME, 100, null, CellType.NUMERIC, java.sql.Time.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return DateUtil.getTimeOnly((Date)DATE_TIME.convertExcelValue(cell)); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + + if (value instanceof Time) + return value; + + if (value instanceof Date) + return DateUtil.getTimeOnly((Date) value); + + try + { + return ConvertUtils.convert(value, Time.class); + } + catch (Exception ignore) + { + } + + return DateUtil.getTimeOnly((Date)DATE_TIME.convert(value)); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.DATE_TIME; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.dateTimeValue = new java.sql.Time(((java.util.Date)value).getTime()); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof Time) + property.dateTimeValue = (Time) value; + else if (null != value) + property.dateTimeValue = (Time) ConvertUtils.convert(value.toString(), Time.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.dateTimeValue; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return NameGenerator.PREVIEW_TIME_VALUE; + } + }, + DOUBLE("http://www.w3.org/2001/XMLSchema#double", "Double", 'f', JdbcType.DOUBLE, 20, null, CellType.NUMERIC, Double.class, Double.TYPE, Float.class, Float.TYPE) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getNumericCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof Double) + return value; + else + return ConvertUtils.convert(String.valueOf(value), Double.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.DOUBLE; + } + + @Override + protected void init(PropertyRow row, Object value) + { + Number n = (Number) value; + if (null != n) + row.floatValue = n.doubleValue(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof Double) + property.floatValue = (Double) value; + else if (null != value) + property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.floatValue; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return 12.34; + } + }, + FLOAT("http://www.w3.org/2001/XMLSchema#float", "Float", 'f', JdbcType.REAL, 20, null, CellType.NUMERIC, Float.class, Float.TYPE) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getNumericCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof Float) + return value; + else + return ConvertUtils.convert(String.valueOf(value), Float.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void init(PropertyRow row, Object value) + { + Number n = (Number) value; + if (null != n) + row.floatValue = n.doubleValue(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof Double) + property.floatValue = (Double) value; + else if (null != value) + property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.floatValue; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return 12.34; + } + }, + DECIMAL("http://www.w3.org/2001/XMLSchema#decimal", "Decimal", 'f', JdbcType.DECIMAL, 20, null, CellType.NUMERIC, BigDecimal.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getNumericCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof BigDecimal) + return value; + else + return ConvertUtils.convert(String.valueOf(value), BigDecimal.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void init(PropertyRow row, Object value) + { + Number n = (Number) value; + if (null != n) + row.floatValue = n.doubleValue(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (null != value) + property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.floatValue; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return 12.34; + } + }, + XML_TEXT("http://cpas.fhcrc.org/exp/xml#text-xml", "XmlText", 's', JdbcType.LONGVARCHAR, 4000, null, CellType.STRING, null) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getStringCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + return STRING.convert(value); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void init(PropertyRow row, Object value) + { + throw new UnsupportedOperationException(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + throw new UnsupportedOperationException(); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.getStringValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return prefix + "Value"; + } + }; + + private final String typeURI; + private final String xarName; + private final char storageType; + private final CellType excelCellType; + private final @NotNull JdbcType jdbcType; + private final int scale; + private final String inputType; + private final Class javaType; + private final Class[] additionalTypes; + + private static Map uriToProperty; + private static Map xarToProperty = null; + + PropertyType(String typeURI, + String xarName, + char storageType, + @NotNull JdbcType jdbcType, + int scale, + String inputType, + CellType excelCellType, + Class javaType, + Class... additionalTypes) + { + this.typeURI = typeURI; + this.xarName = xarName; + this.storageType = storageType; + this.jdbcType = jdbcType; + this.scale = scale; + this.inputType = inputType; + this.javaType = javaType; + this.excelCellType = excelCellType; + this.additionalTypes = additionalTypes; + } + + /** + * The returned Function<Object,Object> should throw ConversionException (undeclared RuntimeException) + */ + + public String getTypeUri() + { + return typeURI; + } + + public String getXmlName() + { + return xarName; + } + + public char getStorageType() + { + return storageType; + } + + @NotNull + public JdbcType getJdbcType() + { + return jdbcType; + } + + public int getScale() + { + return scale; + } + + @Nullable + public String getInputType() + { + return inputType; + } + + public Class getJavaType() + { + return javaType; + } + + public String getXarName() + { + return xarName; + } + + @NotNull + public static PropertyType getFromURI(String concept, String datatype) + { + return getFromURI(concept, datatype, RESOURCE); + } + + @Deprecated // Eliminate this along with PropertyRow? Or at least combine with setValue() below. + abstract protected void init(PropertyRow row, Object value); + abstract protected void setValue(ObjectProperty property, Object value); + abstract protected Object getValue(ObjectProperty property); + public Object getPreviewValue(@Nullable String prefix) + { + return getValue(null); + } + + static + { + Map m = new HashMap<>(); + + for (PropertyType t : values()) + { + String uri = t.getTypeUri(); + m.put(uri, t); + m.put(t.getXmlName(), t); + + if (uri.startsWith("http://www.w3.org/2001/XMLSchema#") || uri.startsWith("http://www.labkey.org/exp/xml#")) + { + String xsdName = uri.substring(uri.indexOf('#') + 1); + m.put("xsd:" + xsdName, t); + m.put(xsdName, t); + } + } + + uriToProperty = m; + } + + public static PropertyType getFromURI(@Nullable String concept, String datatype, PropertyType def) + { + PropertyType p = uriToProperty.get(concept); + + if (null == p) + { + p = uriToProperty.get(datatype); + if (null == p) + p = def; + } + + return p; + } + + @NotNull + public static PropertyType getFromXarName(String xarName) + { + return getFromXarName(xarName, RESOURCE); + } + + public static PropertyType getFromXarName(String xarName, PropertyType def) + { + if (null == xarToProperty) + { + Map m = new CaseInsensitiveHashMap<>(); + for (PropertyType t : values()) + { + m.put(t.getXmlName(), t); + } + xarToProperty = m; + } + + PropertyType p = xarToProperty.get(xarName); + + return null == p ? def : p; + } + + public static PropertyType getFromClass(Class clazz) + { + if (clazz == BigDecimal.class) + clazz = Double.class; + + for (PropertyType t : values()) + { + if (t.javaType == null) + continue; + if (t.javaType.isAssignableFrom(clazz)) + return t; + } + + // after trying the primary types, we then try any additional types: + for (PropertyType t : values()) + { + if (t.additionalTypes == null || t.additionalTypes.length == 0) + continue; + for (Class type : t.additionalTypes) + { + if (type.isAssignableFrom(clazz)) + return t; + } + } + return PropertyType.STRING; + } + + @NotNull + public static PropertyType getFromJdbcType(JdbcType jdbcType) + { + return Objects.requireNonNull(getFromJdbcType(jdbcType, true)); + } + + @Nullable + public static PropertyType getFromJdbcType(JdbcType jdbcType, boolean throwIfNotFound) + { + for (PropertyType t : values()) + { + if (t.jdbcType.equals(jdbcType)) + return t; + } + if (throwIfNotFound) + throw new IllegalArgumentException("No such JdbcType mapping: " + (null != jdbcType ? jdbcType.getClass().toString() : "null")); + else + return null; + } + + @Nullable + public static PropertyType getFromJdbcTypeName(String typeName) + { + for (PropertyType t : values()) + { + if (typeName.equalsIgnoreCase(t.jdbcType.name())) + return t; + } + return null; + } + + public abstract SimpleTypeNames.Enum getXmlBeanType(); + + protected abstract Object convertExcelValue(Cell cell) throws ConversionException; + + public static Object getFromExcelCell(Cell cell) throws ConversionException + { + if (ExcelFactory.isCellNumeric(cell)) + { + // Ugly, the POI implementation doesn't expose an explicit date type + if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) + return DATE_TIME.convertExcelValue(cell); + else + // special handling for the "number type": prefer double. + // Without this, we'd default to integer + return DOUBLE.convertExcelValue(cell); + } + + for (PropertyType t : values()) + { + if (t.excelCellType == cell.getCellType()) + return t.convertExcelValue(cell); + } + return ExcelFactory.getCellStringValue(cell); + } + + public String getValueTypeColumn() + { + switch (this.getStorageType()) + { + case 's': + return "stringValue"; + case 'd': + return "dateTimeValue"; + case 'f': + return "floatValue"; + default: + throw new IllegalArgumentException("Unknown property type: " + this); + } + } +} diff --git a/api/src/org/labkey/api/exp/property/DomainUtil.java b/api/src/org/labkey/api/exp/property/DomainUtil.java index 0485923059c..10d07a8d36f 100644 --- a/api/src/org/labkey/api/exp/property/DomainUtil.java +++ b/api/src/org/labkey/api/exp/property/DomainUtil.java @@ -935,8 +935,16 @@ public static ValidationException updateDomainDescriptor(GWTDomain> propTextChoiceValueUpdates = updatePropertyValidators(p, old, pd); - if (propTextChoiceValueUpdates != null) + if (propTextChoiceValueUpdates != null && !propTextChoiceValueUpdates.isEmpty()) + { + if (PropertyType.MULTI_CHOICE.getTypeUri().equals(old.getRangeURI())) + { + // GitHub Issue 923: Renamed text choice option while converting MV to SV text choice results in bad values + validationException.addError(new SimpleValidationError("Text choice value updates are not supported for multi-choice field: " + p.getName())); + return validationException; + } textChoiceValueUpdates.put(p, propTextChoiceValueUpdates); + } if (old.equals(pd)) continue; @@ -1377,8 +1385,11 @@ private static void updateTextChoiceValueRows(Domain domain, User user, String p Set rowContainers = rows.stream().map((row) -> (String) row.get(containerFieldName)).collect(Collectors.toSet()); for (String rowContainer : rowContainers) { + // GitHub Issue 924: Updating Single Text choice values errors when there are child folders + var dataContainer = ContainerManager.getForId(rowContainer); + var domainTable_ = domain.getDomainKind().getTableInfo(user, dataContainer, domain, ContainerFilter.getUnsafeEverythingFilter()); List> containerRows = rows.stream().filter((row) -> row.get(containerFieldName).equals(rowContainer)).collect(Collectors.toList()); - domainTable.getUpdateService().updateRows(user, ContainerManager.getForId(rowContainer), containerRows, containerRows, batchErrors, Map.of(AuditBehavior, AuditBehaviorType.DETAILED), null); + domainTable_.getUpdateService().updateRows(user, dataContainer, containerRows, containerRows, batchErrors, Map.of(AuditBehavior, AuditBehaviorType.DETAILED), null); } } else diff --git a/api/src/org/labkey/api/reader/DataLoader.java b/api/src/org/labkey/api/reader/DataLoader.java index fa3df2b0052..a500d443ea4 100644 --- a/api/src/org/labkey/api/reader/DataLoader.java +++ b/api/src/org/labkey/api/reader/DataLoader.java @@ -33,6 +33,7 @@ import org.labkey.api.data.Container; import org.labkey.api.data.ImportAliasable; import org.labkey.api.data.JdbcType; +import org.labkey.api.data.MultiChoice; import org.labkey.api.data.MvUtil; import org.labkey.api.dataiterator.DataIterator; import org.labkey.api.dataiterator.DataIteratorBuilder; @@ -825,6 +826,12 @@ else if (column.isMvIndicator()) values[i] = mvWrapper; } } + else if (column.clazz == MultiChoice.Array.class) + { + // GitHub Issue 925: Not providing a MVTC value in an assay result throws error + // convert blank to empty array, not null + values[i] = column.converter.convert(column.clazz, fld); + } else { values[i] = ("".equals(fld)) ? From cd8f6e8d2492b20f7b6ddc56cff98f15b227bfa7 Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 11 Mar 2026 21:52:52 -0700 Subject: [PATCH 2/4] crlf --- api/src/org/labkey/api/exp/PropertyType.java | 2432 +++++++++--------- 1 file changed, 1216 insertions(+), 1216 deletions(-) diff --git a/api/src/org/labkey/api/exp/PropertyType.java b/api/src/org/labkey/api/exp/PropertyType.java index b32731c6ef2..b3ab3c8017a 100644 --- a/api/src/org/labkey/api/exp/PropertyType.java +++ b/api/src/org/labkey/api/exp/PropertyType.java @@ -1,1216 +1,1216 @@ -/* - * 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.exp; - -import org.apache.commons.beanutils.ConversionException; -import org.apache.commons.beanutils.ConvertUtils; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.CellType; -import org.fhcrc.cpas.exp.xml.SimpleTypeNames; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.attachments.AttachmentFile; -import org.labkey.api.collections.CaseInsensitiveHashMap; -import org.labkey.api.data.JdbcType; -import org.labkey.api.data.MultiChoice; -import org.labkey.api.data.NameGenerator; -import org.labkey.api.data.SimpleConvert; -import org.labkey.api.exp.OntologyManager.PropertyRow; -import org.labkey.api.reader.ExcelFactory; -import org.labkey.api.util.DateUtil; -import org.labkey.vfs.FileLike; - -import java.io.File; -import java.math.BigDecimal; -import java.nio.ByteBuffer; -import java.sql.Time; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TimeZone; - -import static org.labkey.api.util.IntegerUtils.asIntegerElseNull; -import static org.labkey.api.util.IntegerUtils.asLongElseNull; - -/** - * TODO: Add more types? Entity, Lsid, User, ... - */ -public enum PropertyType implements SimpleConvert -{ - BOOLEAN("http://www.w3.org/2001/XMLSchema#boolean", "Boolean", 'f', JdbcType.BOOLEAN, 10, null, CellType.BOOLEAN, Boolean.class, Boolean.TYPE) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getBooleanCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - Boolean boolValue = null; - if (value instanceof Boolean) - boolValue = (Boolean)value; - else if (null != value && !"".equals(value)) - boolValue = (Boolean) ConvertUtils.convert(value.toString(), Boolean.class); - return boolValue; - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.BOOLEAN; - } - - @Override - protected void init(PropertyRow row, Object value) - { - Boolean b = (Boolean)value; - row.floatValue = b == Boolean.TRUE ? 1.0 : 0.0; - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - Boolean boolValue = null; - if (value instanceof Boolean) - boolValue = (Boolean)value; - else if (null != value) - boolValue = (Boolean) ConvertUtils.convert(value.toString(), Boolean.class); - property.floatValue = boolValue == null ? null : boolValue == Boolean.TRUE ? 1.0 : 0.0; - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.floatValue == null ? null : property.floatValue.intValue() != 0 ? Boolean.TRUE : Boolean.FALSE; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return Boolean.TRUE; - } - }, - STRING("http://www.w3.org/2001/XMLSchema#string", "String", 's', JdbcType.VARCHAR, 4000, "text", CellType.STRING, String.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getStringCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return value; - if (value instanceof CharSequence cs) - return cs.isEmpty() ? null : cs.toString(); - return ConvertUtils.convert(value, String.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.STRING; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.stringValue = (String)value; - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - property.stringValue = value == null ? null : value.toString(); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.getStringValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return prefix + "Value"; - } - }, - // NOT an XMLSchema type uri??? - MULTI_LINE("http://www.w3.org/2001/XMLSchema#multiLine", "MultiLine", 's', JdbcType.VARCHAR, 4000, "textarea", CellType.STRING, String.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getStringCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - return STRING.convert(value); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.STRING; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.stringValue = (String)value; - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - property.stringValue = value == null ? null : value.toString(); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.getStringValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return prefix + "Value"; - } - }, - MULTI_CHOICE("http://cpas.fhcrc.org/exp/xml#multiChoice", "MultiChoice", '?' /* unsupported in exp.PropertyValues */, JdbcType.ARRAY, 0, "textarea", CellType.STRING, MultiChoice.Array.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return ConvertUtils.convert(cell.getStringCellValue(), MultiChoice.Array.class); - } - - @Override - public Object convert(Object value) throws ConversionException - { - return MultiChoice.Converter.getInstance().convert(MultiChoice.Array.class, value); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.STRING; - } - - @Override - protected void init(PropertyRow row, Object value) - { - throw new UnsupportedOperationException("TODO MultiChoice"); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - property.arrayValue = MultiChoice.Converter.getInstance().convert(MultiChoice.Array.class, value); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.arrayValue; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return "Option 1, Option 2"; - } - }, - RESOURCE("http://www.w3.org/2000/01/rdf-schema#Resource", "PropertyURI", 's', JdbcType.VARCHAR, 4000, null, CellType.STRING, Identifiable.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getStringCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof Identifiable) - return ((Identifiable) value).getLSID(); - else - return value.toString(); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.STRING; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.stringValue = (String)value; - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof Identifiable) - { - property.stringValue = ((Identifiable) value).getLSID(); - property.objectValue = (Identifiable) value; - } - else if (null != value) - property.stringValue = value.toString(); - } - - @Override - protected Object getValue(ObjectProperty property) - { - if (null != property.objectValue) - return property.objectValue; - else - return property.getStringValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return prefix + "Value"; - } - }, - INTEGER("http://www.w3.org/2001/XMLSchema#int", "Integer", 'f', JdbcType.INTEGER, 10, null, CellType.NUMERIC, Integer.class, Integer.TYPE, Long.class, Long.TYPE) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return (int)cell.getNumericCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (asIntegerElseNull(value) instanceof Integer i) - return i; - else - return ConvertUtils.convert(value.toString(), Integer.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.INTEGER; - } - - @Override - protected void init(PropertyRow row, Object value) - { - Number n = (Number) value; - if (null != n) - row.floatValue = n.doubleValue(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (null == value) - property.floatValue = null; - else if (asIntegerElseNull(value) instanceof Integer i) - property.floatValue = i.doubleValue(); - else - property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.floatValue == null ? null : property.floatValue.intValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return Integer.valueOf(3); - } - }, - BIGINT("http://www.w3.org/2001/XMLSchema#long", "Long", 'f', JdbcType.BIGINT, 10, null, CellType.NUMERIC, Long.class, Long.TYPE) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return (int)cell.getNumericCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (asLongElseNull(value) instanceof Long l) - return l; - else - return ConvertUtils.convert(value.toString(), Long.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - throw new UnsupportedOperationException(); - } - - @Override - protected void init(PropertyRow row, Object value) - { - Number n = (Number) value; - if (null != n) - row.floatValue = n.doubleValue(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (null == value) - property.floatValue = null; - else if (asLongElseNull(value) instanceof Long l) - property.floatValue = l.doubleValue(); - else - property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.floatValue == null ? null : property.floatValue.longValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return Integer.valueOf(3); - } - }, - // NOT an XMLSchema type uri??? - BINARY("http://www.w3.org/2001/XMLSchema#binary", "Binary", 'f', JdbcType.BINARY, 10, null, CellType.NUMERIC, ByteBuffer.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return (int)cell.getNumericCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof ByteBuffer) - return value; - else - return ConvertUtils.convert(value.toString(), ByteBuffer.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - throw new UnsupportedOperationException(); - } - - @Override - protected void init(PropertyRow row, Object value) - { - throw new UnsupportedOperationException(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (null != value) - property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - throw new UnsupportedOperationException(); - } - }, - /** Stored as a path to a file on the server's file system */ - FILE_LINK("http://cpas.fhcrc.org/exp/xml#fileLink", "FileLink", 's', JdbcType.VARCHAR, 400, "file", CellType.STRING, File.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getStringCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof File) - return ((File) value).getPath(); - else - return String.valueOf(value); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.FILE_LINK; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.stringValue = (String)value; - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof File f) - property.stringValue = f.getPath(); - else if (value instanceof FileLike fl) - property.stringValue = fl.toNioPathForRead().toString(); - else - property.stringValue = value == null ? null : value.toString(); - } - - @Override - protected Object getValue(ObjectProperty property) - { - String value = property.getStringValue(); - return value == null ? null : new File(value); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return prefix + "Value"; - } - }, - /** Stored in the database as a BLOB using AttachmentService */ - ATTACHMENT("http://www.labkey.org/exp/xml#attachment", "Attachment", 's', JdbcType.VARCHAR, 100, "file", CellType.STRING, File.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getStringCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof File) - return ((File) value).getPath(); - else - return String.valueOf(value); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - throw new UnsupportedOperationException(); - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.stringValue = (String)value; - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof AttachmentFile) - { - property.stringValue = ((AttachmentFile)value).getFilename(); - } - else - property.stringValue = value == null ? null : value.toString(); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.getStringValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return prefix + "Value"; - } - }, - DATE_TIME("http://www.w3.org/2001/XMLSchema#dateTime", "DateTime", 'd', JdbcType.TIMESTAMP, 100, null, CellType.NUMERIC, Date.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - Date date = cell.getDateCellValue(); - if (date != null) - { - DateFormat format = new SimpleDateFormat("MM/dd/yyyy GG HH:mm:ss.SSS"); - format.setTimeZone(TimeZone.getDefault()); - String s = format.format(date); - try - { - date = format.parse(s); - } - catch (ParseException e) - { - throw new ConversionException(e); - } - } - return date; - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof Date) - return value; - else - { - String strVal = value.toString(); - if (DateUtil.isSignedDuration(strVal)) - strVal = JdbcType.TIMESTAMP.convert(value).toString(); - return ConvertUtils.convert(strVal, java.sql.Timestamp.class); - } - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.DATE_TIME; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.dateTimeValue = new java.sql.Time(((java.util.Date)value).getTime()); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof Date) - property.dateTimeValue = (Date) value; - else if (null != value) - property.dateTimeValue = (Date) ConvertUtils.convert(value.toString(), Date.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.dateTimeValue; - } - - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return NameGenerator.PREVIEW_DATETIME_VALUE; - } - }, - DATE("http://www.w3.org/2001/XMLSchema#date", "Date", 'd', JdbcType.DATE, 100, null, CellType.NUMERIC, Date.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return DateUtil.getDateOnly((Date)DATE_TIME.convertExcelValue(cell)); - } - - @Override - public Object convert(Object value) throws ConversionException - { - return DateUtil.getDateOnly((Date)DATE_TIME.convert(value)); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.DATE_TIME; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.dateTimeValue = new java.sql.Date(((java.util.Date)value).getTime()); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof Date) - property.dateTimeValue = (Date) value; - else if (null != value) - property.dateTimeValue = (Date) ConvertUtils.convert(value.toString(), Date.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.dateTimeValue; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return NameGenerator.PREVIEW_DATE_VALUE; - } - }, - TIME("http://www.w3.org/2001/XMLSchema#time", "Time", 'd', JdbcType.TIME, 100, null, CellType.NUMERIC, java.sql.Time.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return DateUtil.getTimeOnly((Date)DATE_TIME.convertExcelValue(cell)); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - - if (value instanceof Time) - return value; - - if (value instanceof Date) - return DateUtil.getTimeOnly((Date) value); - - try - { - return ConvertUtils.convert(value, Time.class); - } - catch (Exception ignore) - { - } - - return DateUtil.getTimeOnly((Date)DATE_TIME.convert(value)); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.DATE_TIME; - } - - @Override - protected void init(PropertyRow row, Object value) - { - row.dateTimeValue = new java.sql.Time(((java.util.Date)value).getTime()); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof Time) - property.dateTimeValue = (Time) value; - else if (null != value) - property.dateTimeValue = (Time) ConvertUtils.convert(value.toString(), Time.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.dateTimeValue; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return NameGenerator.PREVIEW_TIME_VALUE; - } - }, - DOUBLE("http://www.w3.org/2001/XMLSchema#double", "Double", 'f', JdbcType.DOUBLE, 20, null, CellType.NUMERIC, Double.class, Double.TYPE, Float.class, Float.TYPE) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getNumericCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof Double) - return value; - else - return ConvertUtils.convert(String.valueOf(value), Double.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - return SimpleTypeNames.DOUBLE; - } - - @Override - protected void init(PropertyRow row, Object value) - { - Number n = (Number) value; - if (null != n) - row.floatValue = n.doubleValue(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof Double) - property.floatValue = (Double) value; - else if (null != value) - property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.floatValue; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return 12.34; - } - }, - FLOAT("http://www.w3.org/2001/XMLSchema#float", "Float", 'f', JdbcType.REAL, 20, null, CellType.NUMERIC, Float.class, Float.TYPE) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getNumericCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof Float) - return value; - else - return ConvertUtils.convert(String.valueOf(value), Float.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - throw new UnsupportedOperationException(); - } - - @Override - protected void init(PropertyRow row, Object value) - { - Number n = (Number) value; - if (null != n) - row.floatValue = n.doubleValue(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (value instanceof Double) - property.floatValue = (Double) value; - else if (null != value) - property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.floatValue; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return 12.34; - } - }, - DECIMAL("http://www.w3.org/2001/XMLSchema#decimal", "Decimal", 'f', JdbcType.DECIMAL, 20, null, CellType.NUMERIC, BigDecimal.class) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getNumericCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - if (null == value) - return null; - if (value instanceof BigDecimal) - return value; - else - return ConvertUtils.convert(String.valueOf(value), BigDecimal.class); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - throw new UnsupportedOperationException(); - } - - @Override - protected void init(PropertyRow row, Object value) - { - Number n = (Number) value; - if (null != n) - row.floatValue = n.doubleValue(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - if (null != value) - property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.floatValue; - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return 12.34; - } - }, - XML_TEXT("http://cpas.fhcrc.org/exp/xml#text-xml", "XmlText", 's', JdbcType.LONGVARCHAR, 4000, null, CellType.STRING, null) - { - @Override - protected Object convertExcelValue(Cell cell) throws ConversionException - { - return cell.getStringCellValue(); - } - - @Override - public Object convert(Object value) throws ConversionException - { - return STRING.convert(value); - } - - @Override - public SimpleTypeNames.Enum getXmlBeanType() - { - throw new UnsupportedOperationException(); - } - - @Override - protected void init(PropertyRow row, Object value) - { - throw new UnsupportedOperationException(); - } - - @Override - protected void setValue(ObjectProperty property, Object value) - { - throw new UnsupportedOperationException(); - } - - @Override - protected Object getValue(ObjectProperty property) - { - return property.getStringValue(); - } - - @Override - public Object getPreviewValue(@Nullable String prefix) - { - return prefix + "Value"; - } - }; - - private final String typeURI; - private final String xarName; - private final char storageType; - private final CellType excelCellType; - private final @NotNull JdbcType jdbcType; - private final int scale; - private final String inputType; - private final Class javaType; - private final Class[] additionalTypes; - - private static Map uriToProperty; - private static Map xarToProperty = null; - - PropertyType(String typeURI, - String xarName, - char storageType, - @NotNull JdbcType jdbcType, - int scale, - String inputType, - CellType excelCellType, - Class javaType, - Class... additionalTypes) - { - this.typeURI = typeURI; - this.xarName = xarName; - this.storageType = storageType; - this.jdbcType = jdbcType; - this.scale = scale; - this.inputType = inputType; - this.javaType = javaType; - this.excelCellType = excelCellType; - this.additionalTypes = additionalTypes; - } - - /** - * The returned Function<Object,Object> should throw ConversionException (undeclared RuntimeException) - */ - - public String getTypeUri() - { - return typeURI; - } - - public String getXmlName() - { - return xarName; - } - - public char getStorageType() - { - return storageType; - } - - @NotNull - public JdbcType getJdbcType() - { - return jdbcType; - } - - public int getScale() - { - return scale; - } - - @Nullable - public String getInputType() - { - return inputType; - } - - public Class getJavaType() - { - return javaType; - } - - public String getXarName() - { - return xarName; - } - - @NotNull - public static PropertyType getFromURI(String concept, String datatype) - { - return getFromURI(concept, datatype, RESOURCE); - } - - @Deprecated // Eliminate this along with PropertyRow? Or at least combine with setValue() below. - abstract protected void init(PropertyRow row, Object value); - abstract protected void setValue(ObjectProperty property, Object value); - abstract protected Object getValue(ObjectProperty property); - public Object getPreviewValue(@Nullable String prefix) - { - return getValue(null); - } - - static - { - Map m = new HashMap<>(); - - for (PropertyType t : values()) - { - String uri = t.getTypeUri(); - m.put(uri, t); - m.put(t.getXmlName(), t); - - if (uri.startsWith("http://www.w3.org/2001/XMLSchema#") || uri.startsWith("http://www.labkey.org/exp/xml#")) - { - String xsdName = uri.substring(uri.indexOf('#') + 1); - m.put("xsd:" + xsdName, t); - m.put(xsdName, t); - } - } - - uriToProperty = m; - } - - public static PropertyType getFromURI(@Nullable String concept, String datatype, PropertyType def) - { - PropertyType p = uriToProperty.get(concept); - - if (null == p) - { - p = uriToProperty.get(datatype); - if (null == p) - p = def; - } - - return p; - } - - @NotNull - public static PropertyType getFromXarName(String xarName) - { - return getFromXarName(xarName, RESOURCE); - } - - public static PropertyType getFromXarName(String xarName, PropertyType def) - { - if (null == xarToProperty) - { - Map m = new CaseInsensitiveHashMap<>(); - for (PropertyType t : values()) - { - m.put(t.getXmlName(), t); - } - xarToProperty = m; - } - - PropertyType p = xarToProperty.get(xarName); - - return null == p ? def : p; - } - - public static PropertyType getFromClass(Class clazz) - { - if (clazz == BigDecimal.class) - clazz = Double.class; - - for (PropertyType t : values()) - { - if (t.javaType == null) - continue; - if (t.javaType.isAssignableFrom(clazz)) - return t; - } - - // after trying the primary types, we then try any additional types: - for (PropertyType t : values()) - { - if (t.additionalTypes == null || t.additionalTypes.length == 0) - continue; - for (Class type : t.additionalTypes) - { - if (type.isAssignableFrom(clazz)) - return t; - } - } - return PropertyType.STRING; - } - - @NotNull - public static PropertyType getFromJdbcType(JdbcType jdbcType) - { - return Objects.requireNonNull(getFromJdbcType(jdbcType, true)); - } - - @Nullable - public static PropertyType getFromJdbcType(JdbcType jdbcType, boolean throwIfNotFound) - { - for (PropertyType t : values()) - { - if (t.jdbcType.equals(jdbcType)) - return t; - } - if (throwIfNotFound) - throw new IllegalArgumentException("No such JdbcType mapping: " + (null != jdbcType ? jdbcType.getClass().toString() : "null")); - else - return null; - } - - @Nullable - public static PropertyType getFromJdbcTypeName(String typeName) - { - for (PropertyType t : values()) - { - if (typeName.equalsIgnoreCase(t.jdbcType.name())) - return t; - } - return null; - } - - public abstract SimpleTypeNames.Enum getXmlBeanType(); - - protected abstract Object convertExcelValue(Cell cell) throws ConversionException; - - public static Object getFromExcelCell(Cell cell) throws ConversionException - { - if (ExcelFactory.isCellNumeric(cell)) - { - // Ugly, the POI implementation doesn't expose an explicit date type - if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) - return DATE_TIME.convertExcelValue(cell); - else - // special handling for the "number type": prefer double. - // Without this, we'd default to integer - return DOUBLE.convertExcelValue(cell); - } - - for (PropertyType t : values()) - { - if (t.excelCellType == cell.getCellType()) - return t.convertExcelValue(cell); - } - return ExcelFactory.getCellStringValue(cell); - } - - public String getValueTypeColumn() - { - switch (this.getStorageType()) - { - case 's': - return "stringValue"; - case 'd': - return "dateTimeValue"; - case 'f': - return "floatValue"; - default: - throw new IllegalArgumentException("Unknown property type: " + this); - } - } -} +/* + * 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.exp; + +import org.apache.commons.beanutils.ConversionException; +import org.apache.commons.beanutils.ConvertUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.fhcrc.cpas.exp.xml.SimpleTypeNames; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.labkey.api.attachments.AttachmentFile; +import org.labkey.api.collections.CaseInsensitiveHashMap; +import org.labkey.api.data.JdbcType; +import org.labkey.api.data.MultiChoice; +import org.labkey.api.data.NameGenerator; +import org.labkey.api.data.SimpleConvert; +import org.labkey.api.exp.OntologyManager.PropertyRow; +import org.labkey.api.reader.ExcelFactory; +import org.labkey.api.util.DateUtil; +import org.labkey.vfs.FileLike; + +import java.io.File; +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.sql.Time; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TimeZone; + +import static org.labkey.api.util.IntegerUtils.asIntegerElseNull; +import static org.labkey.api.util.IntegerUtils.asLongElseNull; + +/** + * TODO: Add more types? Entity, Lsid, User, ... + */ +public enum PropertyType implements SimpleConvert +{ + BOOLEAN("http://www.w3.org/2001/XMLSchema#boolean", "Boolean", 'f', JdbcType.BOOLEAN, 10, null, CellType.BOOLEAN, Boolean.class, Boolean.TYPE) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getBooleanCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + Boolean boolValue = null; + if (value instanceof Boolean) + boolValue = (Boolean)value; + else if (null != value && !"".equals(value)) + boolValue = (Boolean) ConvertUtils.convert(value.toString(), Boolean.class); + return boolValue; + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.BOOLEAN; + } + + @Override + protected void init(PropertyRow row, Object value) + { + Boolean b = (Boolean)value; + row.floatValue = b == Boolean.TRUE ? 1.0 : 0.0; + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + Boolean boolValue = null; + if (value instanceof Boolean) + boolValue = (Boolean)value; + else if (null != value) + boolValue = (Boolean) ConvertUtils.convert(value.toString(), Boolean.class); + property.floatValue = boolValue == null ? null : boolValue == Boolean.TRUE ? 1.0 : 0.0; + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.floatValue == null ? null : property.floatValue.intValue() != 0 ? Boolean.TRUE : Boolean.FALSE; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return Boolean.TRUE; + } + }, + STRING("http://www.w3.org/2001/XMLSchema#string", "String", 's', JdbcType.VARCHAR, 4000, "text", CellType.STRING, String.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getStringCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return value; + if (value instanceof CharSequence cs) + return cs.isEmpty() ? null : cs.toString(); + return ConvertUtils.convert(value, String.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.STRING; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.stringValue = (String)value; + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + property.stringValue = value == null ? null : value.toString(); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.getStringValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return prefix + "Value"; + } + }, + // NOT an XMLSchema type uri??? + MULTI_LINE("http://www.w3.org/2001/XMLSchema#multiLine", "MultiLine", 's', JdbcType.VARCHAR, 4000, "textarea", CellType.STRING, String.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getStringCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + return STRING.convert(value); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.STRING; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.stringValue = (String)value; + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + property.stringValue = value == null ? null : value.toString(); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.getStringValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return prefix + "Value"; + } + }, + MULTI_CHOICE("http://cpas.fhcrc.org/exp/xml#multiChoice", "MultiChoice", '?' /* unsupported in exp.PropertyValues */, JdbcType.ARRAY, 0, "textarea", CellType.STRING, MultiChoice.Array.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return ConvertUtils.convert(cell.getStringCellValue(), MultiChoice.Array.class); + } + + @Override + public Object convert(Object value) throws ConversionException + { + return MultiChoice.Converter.getInstance().convert(MultiChoice.Array.class, value); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.STRING; + } + + @Override + protected void init(PropertyRow row, Object value) + { + throw new UnsupportedOperationException("TODO MultiChoice"); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + property.arrayValue = MultiChoice.Converter.getInstance().convert(MultiChoice.Array.class, value); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.arrayValue; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return "Option 1, Option 2"; + } + }, + RESOURCE("http://www.w3.org/2000/01/rdf-schema#Resource", "PropertyURI", 's', JdbcType.VARCHAR, 4000, null, CellType.STRING, Identifiable.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getStringCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof Identifiable) + return ((Identifiable) value).getLSID(); + else + return value.toString(); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.STRING; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.stringValue = (String)value; + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof Identifiable) + { + property.stringValue = ((Identifiable) value).getLSID(); + property.objectValue = (Identifiable) value; + } + else if (null != value) + property.stringValue = value.toString(); + } + + @Override + protected Object getValue(ObjectProperty property) + { + if (null != property.objectValue) + return property.objectValue; + else + return property.getStringValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return prefix + "Value"; + } + }, + INTEGER("http://www.w3.org/2001/XMLSchema#int", "Integer", 'f', JdbcType.INTEGER, 10, null, CellType.NUMERIC, Integer.class, Integer.TYPE, Long.class, Long.TYPE) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return (int)cell.getNumericCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (asIntegerElseNull(value) instanceof Integer i) + return i; + else + return ConvertUtils.convert(value.toString(), Integer.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.INTEGER; + } + + @Override + protected void init(PropertyRow row, Object value) + { + Number n = (Number) value; + if (null != n) + row.floatValue = n.doubleValue(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (null == value) + property.floatValue = null; + else if (asIntegerElseNull(value) instanceof Integer i) + property.floatValue = i.doubleValue(); + else + property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.floatValue == null ? null : property.floatValue.intValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return Integer.valueOf(3); + } + }, + BIGINT("http://www.w3.org/2001/XMLSchema#long", "Long", 'f', JdbcType.BIGINT, 10, null, CellType.NUMERIC, Long.class, Long.TYPE) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return (int)cell.getNumericCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (asLongElseNull(value) instanceof Long l) + return l; + else + return ConvertUtils.convert(value.toString(), Long.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void init(PropertyRow row, Object value) + { + Number n = (Number) value; + if (null != n) + row.floatValue = n.doubleValue(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (null == value) + property.floatValue = null; + else if (asLongElseNull(value) instanceof Long l) + property.floatValue = l.doubleValue(); + else + property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.floatValue == null ? null : property.floatValue.longValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return Integer.valueOf(3); + } + }, + // NOT an XMLSchema type uri??? + BINARY("http://www.w3.org/2001/XMLSchema#binary", "Binary", 'f', JdbcType.BINARY, 10, null, CellType.NUMERIC, ByteBuffer.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return (int)cell.getNumericCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof ByteBuffer) + return value; + else + return ConvertUtils.convert(value.toString(), ByteBuffer.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void init(PropertyRow row, Object value) + { + throw new UnsupportedOperationException(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (null != value) + property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + throw new UnsupportedOperationException(); + } + }, + /** Stored as a path to a file on the server's file system */ + FILE_LINK("http://cpas.fhcrc.org/exp/xml#fileLink", "FileLink", 's', JdbcType.VARCHAR, 400, "file", CellType.STRING, File.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getStringCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof File) + return ((File) value).getPath(); + else + return String.valueOf(value); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.FILE_LINK; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.stringValue = (String)value; + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof File f) + property.stringValue = f.getPath(); + else if (value instanceof FileLike fl) + property.stringValue = fl.toNioPathForRead().toString(); + else + property.stringValue = value == null ? null : value.toString(); + } + + @Override + protected Object getValue(ObjectProperty property) + { + String value = property.getStringValue(); + return value == null ? null : new File(value); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return prefix + "Value"; + } + }, + /** Stored in the database as a BLOB using AttachmentService */ + ATTACHMENT("http://www.labkey.org/exp/xml#attachment", "Attachment", 's', JdbcType.VARCHAR, 100, "file", CellType.STRING, File.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getStringCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof File) + return ((File) value).getPath(); + else + return String.valueOf(value); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.stringValue = (String)value; + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof AttachmentFile) + { + property.stringValue = ((AttachmentFile)value).getFilename(); + } + else + property.stringValue = value == null ? null : value.toString(); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.getStringValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return prefix + "Value"; + } + }, + DATE_TIME("http://www.w3.org/2001/XMLSchema#dateTime", "DateTime", 'd', JdbcType.TIMESTAMP, 100, null, CellType.NUMERIC, Date.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + Date date = cell.getDateCellValue(); + if (date != null) + { + DateFormat format = new SimpleDateFormat("MM/dd/yyyy GG HH:mm:ss.SSS"); + format.setTimeZone(TimeZone.getDefault()); + String s = format.format(date); + try + { + date = format.parse(s); + } + catch (ParseException e) + { + throw new ConversionException(e); + } + } + return date; + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof Date) + return value; + else + { + String strVal = value.toString(); + if (DateUtil.isSignedDuration(strVal)) + strVal = JdbcType.TIMESTAMP.convert(value).toString(); + return ConvertUtils.convert(strVal, java.sql.Timestamp.class); + } + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.DATE_TIME; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.dateTimeValue = new java.sql.Time(((java.util.Date)value).getTime()); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof Date) + property.dateTimeValue = (Date) value; + else if (null != value) + property.dateTimeValue = (Date) ConvertUtils.convert(value.toString(), Date.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.dateTimeValue; + } + + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return NameGenerator.PREVIEW_DATETIME_VALUE; + } + }, + DATE("http://www.w3.org/2001/XMLSchema#date", "Date", 'd', JdbcType.DATE, 100, null, CellType.NUMERIC, Date.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return DateUtil.getDateOnly((Date)DATE_TIME.convertExcelValue(cell)); + } + + @Override + public Object convert(Object value) throws ConversionException + { + return DateUtil.getDateOnly((Date)DATE_TIME.convert(value)); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.DATE_TIME; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.dateTimeValue = new java.sql.Date(((java.util.Date)value).getTime()); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof Date) + property.dateTimeValue = (Date) value; + else if (null != value) + property.dateTimeValue = (Date) ConvertUtils.convert(value.toString(), Date.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.dateTimeValue; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return NameGenerator.PREVIEW_DATE_VALUE; + } + }, + TIME("http://www.w3.org/2001/XMLSchema#time", "Time", 'd', JdbcType.TIME, 100, null, CellType.NUMERIC, java.sql.Time.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return DateUtil.getTimeOnly((Date)DATE_TIME.convertExcelValue(cell)); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + + if (value instanceof Time) + return value; + + if (value instanceof Date) + return DateUtil.getTimeOnly((Date) value); + + try + { + return ConvertUtils.convert(value, Time.class); + } + catch (Exception ignore) + { + } + + return DateUtil.getTimeOnly((Date)DATE_TIME.convert(value)); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.DATE_TIME; + } + + @Override + protected void init(PropertyRow row, Object value) + { + row.dateTimeValue = new java.sql.Time(((java.util.Date)value).getTime()); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof Time) + property.dateTimeValue = (Time) value; + else if (null != value) + property.dateTimeValue = (Time) ConvertUtils.convert(value.toString(), Time.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.dateTimeValue; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return NameGenerator.PREVIEW_TIME_VALUE; + } + }, + DOUBLE("http://www.w3.org/2001/XMLSchema#double", "Double", 'f', JdbcType.DOUBLE, 20, null, CellType.NUMERIC, Double.class, Double.TYPE, Float.class, Float.TYPE) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getNumericCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof Double) + return value; + else + return ConvertUtils.convert(String.valueOf(value), Double.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + return SimpleTypeNames.DOUBLE; + } + + @Override + protected void init(PropertyRow row, Object value) + { + Number n = (Number) value; + if (null != n) + row.floatValue = n.doubleValue(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof Double) + property.floatValue = (Double) value; + else if (null != value) + property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.floatValue; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return 12.34; + } + }, + FLOAT("http://www.w3.org/2001/XMLSchema#float", "Float", 'f', JdbcType.REAL, 20, null, CellType.NUMERIC, Float.class, Float.TYPE) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getNumericCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof Float) + return value; + else + return ConvertUtils.convert(String.valueOf(value), Float.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void init(PropertyRow row, Object value) + { + Number n = (Number) value; + if (null != n) + row.floatValue = n.doubleValue(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (value instanceof Double) + property.floatValue = (Double) value; + else if (null != value) + property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.floatValue; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return 12.34; + } + }, + DECIMAL("http://www.w3.org/2001/XMLSchema#decimal", "Decimal", 'f', JdbcType.DECIMAL, 20, null, CellType.NUMERIC, BigDecimal.class) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getNumericCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + if (null == value) + return null; + if (value instanceof BigDecimal) + return value; + else + return ConvertUtils.convert(String.valueOf(value), BigDecimal.class); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void init(PropertyRow row, Object value) + { + Number n = (Number) value; + if (null != n) + row.floatValue = n.doubleValue(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + if (null != value) + property.floatValue = (Double) ConvertUtils.convert(value.toString(), Double.class); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.floatValue; + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return 12.34; + } + }, + XML_TEXT("http://cpas.fhcrc.org/exp/xml#text-xml", "XmlText", 's', JdbcType.LONGVARCHAR, 4000, null, CellType.STRING, null) + { + @Override + protected Object convertExcelValue(Cell cell) throws ConversionException + { + return cell.getStringCellValue(); + } + + @Override + public Object convert(Object value) throws ConversionException + { + return STRING.convert(value); + } + + @Override + public SimpleTypeNames.Enum getXmlBeanType() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void init(PropertyRow row, Object value) + { + throw new UnsupportedOperationException(); + } + + @Override + protected void setValue(ObjectProperty property, Object value) + { + throw new UnsupportedOperationException(); + } + + @Override + protected Object getValue(ObjectProperty property) + { + return property.getStringValue(); + } + + @Override + public Object getPreviewValue(@Nullable String prefix) + { + return prefix + "Value"; + } + }; + + private final String typeURI; + private final String xarName; + private final char storageType; + private final CellType excelCellType; + private final @NotNull JdbcType jdbcType; + private final int scale; + private final String inputType; + private final Class javaType; + private final Class[] additionalTypes; + + private static Map uriToProperty; + private static Map xarToProperty = null; + + PropertyType(String typeURI, + String xarName, + char storageType, + @NotNull JdbcType jdbcType, + int scale, + String inputType, + CellType excelCellType, + Class javaType, + Class... additionalTypes) + { + this.typeURI = typeURI; + this.xarName = xarName; + this.storageType = storageType; + this.jdbcType = jdbcType; + this.scale = scale; + this.inputType = inputType; + this.javaType = javaType; + this.excelCellType = excelCellType; + this.additionalTypes = additionalTypes; + } + + /** + * The returned Function<Object,Object> should throw ConversionException (undeclared RuntimeException) + */ + + public String getTypeUri() + { + return typeURI; + } + + public String getXmlName() + { + return xarName; + } + + public char getStorageType() + { + return storageType; + } + + @NotNull + public JdbcType getJdbcType() + { + return jdbcType; + } + + public int getScale() + { + return scale; + } + + @Nullable + public String getInputType() + { + return inputType; + } + + public Class getJavaType() + { + return javaType; + } + + public String getXarName() + { + return xarName; + } + + @NotNull + public static PropertyType getFromURI(String concept, String datatype) + { + return getFromURI(concept, datatype, RESOURCE); + } + + @Deprecated // Eliminate this along with PropertyRow? Or at least combine with setValue() below. + abstract protected void init(PropertyRow row, Object value); + abstract protected void setValue(ObjectProperty property, Object value); + abstract protected Object getValue(ObjectProperty property); + public Object getPreviewValue(@Nullable String prefix) + { + return getValue(null); + } + + static + { + Map m = new HashMap<>(); + + for (PropertyType t : values()) + { + String uri = t.getTypeUri(); + m.put(uri, t); + m.put(t.getXmlName(), t); + + if (uri.startsWith("http://www.w3.org/2001/XMLSchema#") || uri.startsWith("http://www.labkey.org/exp/xml#")) + { + String xsdName = uri.substring(uri.indexOf('#') + 1); + m.put("xsd:" + xsdName, t); + m.put(xsdName, t); + } + } + + uriToProperty = m; + } + + public static PropertyType getFromURI(@Nullable String concept, String datatype, PropertyType def) + { + PropertyType p = uriToProperty.get(concept); + + if (null == p) + { + p = uriToProperty.get(datatype); + if (null == p) + p = def; + } + + return p; + } + + @NotNull + public static PropertyType getFromXarName(String xarName) + { + return getFromXarName(xarName, RESOURCE); + } + + public static PropertyType getFromXarName(String xarName, PropertyType def) + { + if (null == xarToProperty) + { + Map m = new CaseInsensitiveHashMap<>(); + for (PropertyType t : values()) + { + m.put(t.getXmlName(), t); + } + xarToProperty = m; + } + + PropertyType p = xarToProperty.get(xarName); + + return null == p ? def : p; + } + + public static PropertyType getFromClass(Class clazz) + { + if (clazz == BigDecimal.class) + clazz = Double.class; + + for (PropertyType t : values()) + { + if (t.javaType == null) + continue; + if (t.javaType.isAssignableFrom(clazz)) + return t; + } + + // after trying the primary types, we then try any additional types: + for (PropertyType t : values()) + { + if (t.additionalTypes == null || t.additionalTypes.length == 0) + continue; + for (Class type : t.additionalTypes) + { + if (type.isAssignableFrom(clazz)) + return t; + } + } + return PropertyType.STRING; + } + + @NotNull + public static PropertyType getFromJdbcType(JdbcType jdbcType) + { + return Objects.requireNonNull(getFromJdbcType(jdbcType, true)); + } + + @Nullable + public static PropertyType getFromJdbcType(JdbcType jdbcType, boolean throwIfNotFound) + { + for (PropertyType t : values()) + { + if (t.jdbcType.equals(jdbcType)) + return t; + } + if (throwIfNotFound) + throw new IllegalArgumentException("No such JdbcType mapping: " + (null != jdbcType ? jdbcType.getClass().toString() : "null")); + else + return null; + } + + @Nullable + public static PropertyType getFromJdbcTypeName(String typeName) + { + for (PropertyType t : values()) + { + if (typeName.equalsIgnoreCase(t.jdbcType.name())) + return t; + } + return null; + } + + public abstract SimpleTypeNames.Enum getXmlBeanType(); + + protected abstract Object convertExcelValue(Cell cell) throws ConversionException; + + public static Object getFromExcelCell(Cell cell) throws ConversionException + { + if (ExcelFactory.isCellNumeric(cell)) + { + // Ugly, the POI implementation doesn't expose an explicit date type + if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) + return DATE_TIME.convertExcelValue(cell); + else + // special handling for the "number type": prefer double. + // Without this, we'd default to integer + return DOUBLE.convertExcelValue(cell); + } + + for (PropertyType t : values()) + { + if (t.excelCellType == cell.getCellType()) + return t.convertExcelValue(cell); + } + return ExcelFactory.getCellStringValue(cell); + } + + public String getValueTypeColumn() + { + switch (this.getStorageType()) + { + case 's': + return "stringValue"; + case 'd': + return "dateTimeValue"; + case 'f': + return "floatValue"; + default: + throw new IllegalArgumentException("Unknown property type: " + this); + } + } +} From df0f81181c2ecf0a70ac1e85dc5880764f287205 Mon Sep 17 00:00:00 2001 From: XingY Date: Thu, 12 Mar 2026 17:47:32 -0700 Subject: [PATCH 3/4] GitHub Issue 935: Changing from MVTC to TC wraps all values in curly braces --- .../experiment/api/property/StorageProvisionerImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/experiment/src/org/labkey/experiment/api/property/StorageProvisionerImpl.java b/experiment/src/org/labkey/experiment/api/property/StorageProvisionerImpl.java index 933824eadd2..b799e59ad83 100644 --- a/experiment/src/org/labkey/experiment/api/property/StorageProvisionerImpl.java +++ b/experiment/src/org/labkey/experiment/api/property/StorageProvisionerImpl.java @@ -601,7 +601,9 @@ public void changePropertyType(Domain domain, DomainProperty prop) throws Change throw new ChangePropertyDescriptorException("Unable to change property type. There are rows with multiple values stored for '" + prop.getName() + "'."); } } - oldPropTypes.put(prop.getName(), oldPd.getPropertyType()); + // GitHub Issue 935: Changing from MVTC to TC wraps all values in curly braces + // This is due to StorageColumnName differ from column name, resulting in column update skipped + oldPropTypes.put(prop.getPropertyDescriptor().getStorageColumnName(), oldPd.getPropertyType()); } } From 3bbbe64385d46520ee5cc1ad222253811f655068 Mon Sep 17 00:00:00 2001 From: XingY Date: Fri, 13 Mar 2026 14:43:10 -0700 Subject: [PATCH 4/4] GitHub Issue 923: disallow option update when converting from SV to MV --- api/src/org/labkey/api/exp/property/DomainUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/org/labkey/api/exp/property/DomainUtil.java b/api/src/org/labkey/api/exp/property/DomainUtil.java index 10d07a8d36f..303b4445766 100644 --- a/api/src/org/labkey/api/exp/property/DomainUtil.java +++ b/api/src/org/labkey/api/exp/property/DomainUtil.java @@ -937,7 +937,7 @@ public static ValidationException updateDomainDescriptor(GWTDomain> propTextChoiceValueUpdates = updatePropertyValidators(p, old, pd); if (propTextChoiceValueUpdates != null && !propTextChoiceValueUpdates.isEmpty()) { - if (PropertyType.MULTI_CHOICE.getTypeUri().equals(old.getRangeURI())) + if (PropertyType.MULTI_CHOICE.getTypeUri().equals(old.getRangeURI()) || PropertyType.MULTI_CHOICE.getTypeUri().equals(pd.getRangeURI())) { // GitHub Issue 923: Renamed text choice option while converting MV to SV text choice results in bad values validationException.addError(new SimpleValidationError("Text choice value updates are not supported for multi-choice field: " + p.getName()));