From d8c93b290aff41e0833acb439fff0d6773c22423 Mon Sep 17 00:00:00 2001 From: Simon Danner Date: Sat, 15 Feb 2020 17:16:16 +0100 Subject: [PATCH] SQL serialization bug fixes for null and complex value types --- .../serialization/IAutoSerializableField.java | 6 +++ .../serialization/ISerializableField.java | 5 +++ .../serialization/ISerializableFieldJson.java | 6 +++ .../literals/fields/types/DateField.java | 6 +++ .../literals/fields/types/EnumField.java | 6 +++ .../executors/StatementExecutor.java | 7 +++- .../ojcms/sqlbuilder/result/ResultRow.java | 3 +- .../serialization/DefaultValueSerializer.java | 40 +++++++++++++++++-- .../datasource/util/BeanSQLSerializer.java | 18 +++++---- 9 files changed, 84 insertions(+), 13 deletions(-) diff --git a/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/serialization/IAutoSerializableField.java b/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/serialization/IAutoSerializableField.java index 1c434e7..fc4fc32 100644 --- a/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/serialization/IAutoSerializableField.java +++ b/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/serialization/IAutoSerializableField.java @@ -21,4 +21,10 @@ default VALUE fromPersistent(VALUE pSerialValue) { return pSerialValue; } + + @Override + default Class getSerialValueType() + { + return getDataType(); + } } diff --git a/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/serialization/ISerializableField.java b/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/serialization/ISerializableField.java index 848902a..2bd3a2b 100644 --- a/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/serialization/ISerializableField.java +++ b/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/serialization/ISerializableField.java @@ -27,4 +27,9 @@ public interface ISerializableField extends * @return the original field value */ VALUE fromPersistent(SERIAL pSerialValue); + + /** + * The serial value's type. + */ + Class getSerialValueType(); } diff --git a/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/serialization/ISerializableFieldJson.java b/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/serialization/ISerializableFieldJson.java index dc76e13..38c22f1 100644 --- a/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/serialization/ISerializableFieldJson.java +++ b/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/serialization/ISerializableFieldJson.java @@ -21,4 +21,10 @@ default VALUE fromPersistent(String pSerialString) { return new Gson().fromJson(pSerialString, getDataType()); } + + @Override + default Class getSerialValueType() + { + return String.class; + } } diff --git a/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/types/DateField.java b/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/types/DateField.java index 010020c..b89454a 100644 --- a/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/types/DateField.java +++ b/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/types/DateField.java @@ -48,6 +48,12 @@ public Long toPersistent(Instant pValue) return pValue.toEpochMilli(); } + @Override + public Class getSerialValueType() + { + return Long.class; + } + @Override public Instant fromPersistent(Long pSerialValue) { diff --git a/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/types/EnumField.java b/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/types/EnumField.java index 89c4b44..b8a1dae 100644 --- a/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/types/EnumField.java +++ b/ojcms-beans/src/main/java/de/adito/ojcms/beans/literals/fields/types/EnumField.java @@ -49,6 +49,12 @@ public ENUM fromPersistent(String pSerialString) } } + @Override + public Class getSerialValueType() + { + return String.class; + } + @Override public ENUM copyValue(ENUM pEnum, ECopyMode pMode, CustomFieldCopy... pCustomFieldCopies) { diff --git a/ojcms-sqlbuilder/src/main/java/de/adito/ojcms/sqlbuilder/executors/StatementExecutor.java b/ojcms-sqlbuilder/src/main/java/de/adito/ojcms/sqlbuilder/executors/StatementExecutor.java index dc4e237..38172cd 100644 --- a/ojcms-sqlbuilder/src/main/java/de/adito/ojcms/sqlbuilder/executors/StatementExecutor.java +++ b/ojcms-sqlbuilder/src/main/java/de/adito/ojcms/sqlbuilder/executors/StatementExecutor.java @@ -7,6 +7,7 @@ import java.sql.*; import java.util.List; import java.util.function.Supplier; +import java.util.logging.Logger; /** * Implementation of a statement executor based on a function that will be provided with a {@link PreparedStatement}. @@ -17,6 +18,8 @@ */ class StatementExecutor implements IStatementExecutor { + private static final Logger LOGGER = Logger.getLogger(StatementExecutor.class.getName()); + private final Supplier connectionSupplier; private final ThrowingFunction executor; private Connection connection; @@ -47,7 +50,9 @@ public RESULT executeStatement(String pSQLStatement, List pArgs) for (ISerialValue arg : pArgs) arg.applyToStatement(statement, argIndex++); - return executor.apply(statement); + final RESULT result = executor.apply(statement); + LOGGER.info("SQL executed: " + pSQLStatement); + return result; } catch (SQLException pE) { diff --git a/ojcms-sqlbuilder/src/main/java/de/adito/ojcms/sqlbuilder/result/ResultRow.java b/ojcms-sqlbuilder/src/main/java/de/adito/ojcms/sqlbuilder/result/ResultRow.java index e8a2494..488f277 100644 --- a/ojcms-sqlbuilder/src/main/java/de/adito/ojcms/sqlbuilder/result/ResultRow.java +++ b/ojcms-sqlbuilder/src/main/java/de/adito/ojcms/sqlbuilder/result/ResultRow.java @@ -99,7 +99,8 @@ public > Map toMap(COL { //noinspection unchecked return Stream.of(pColumnsToInclude) - .collect(Collectors.toMap(pColumnMapper, pColumn -> get((IColumnIdentification) pColumn))); + //Allow null values + .collect(HashMap::new, (pMap, pColumn) -> pMap.put(pColumnMapper.apply(pColumn), get((IColumnIdentification) pColumn)), HashMap::putAll); } /** diff --git a/ojcms-sqlbuilder/src/main/java/de/adito/ojcms/sqlbuilder/serialization/DefaultValueSerializer.java b/ojcms-sqlbuilder/src/main/java/de/adito/ojcms/sqlbuilder/serialization/DefaultValueSerializer.java index fc58b1f..917e9d8 100644 --- a/ojcms-sqlbuilder/src/main/java/de/adito/ojcms/sqlbuilder/serialization/DefaultValueSerializer.java +++ b/ojcms-sqlbuilder/src/main/java/de/adito/ojcms/sqlbuilder/serialization/DefaultValueSerializer.java @@ -21,6 +21,7 @@ public class DefaultValueSerializer implements IValueSerializer { private static final Map> SUPPORTED_SERIALIZATIONS = new HashMap<>(); + protected static final ISerialValue NULL_SERIAL_VALUE = new _NullSerialValue(); /* * Adds all supported data types. @@ -58,8 +59,7 @@ public ISerialValue toSerial(IColumnValueTuple pColumnValueTuple) @Nullable public VALUE fromSerial(IColumnIdentification pColumn, ResultSet pResultSet, int pIndex) { - final _SupportedSerialization serialization = _getSerializationForType(pColumn.getDataType()); - return serialization.retrieveConvertedResult(pResultSet, pIndex); + return retrieveSerialValue(pColumn.getDataType(), pResultSet, pIndex); } /** @@ -73,7 +73,7 @@ public VALUE fromSerial(IColumnIdentification pColumn, ResultSet protected ISerialValue createSerialValue(Class pDataType, VALUE pDataValue) { if (pDataValue == null) - return null; + return NULL_SERIAL_VALUE; final _SupportedSerialization serialization = _getSerializationForType(pDataType); @@ -96,6 +96,21 @@ public void applyToStatement(PreparedStatement pStatement, int pIndex) }; } + /** + * Retrieves a value from a {@link ResultSet} and converts it back to its original data value. + * + * @param pValueType the value type to retrieve + * @param pResultSet the SQL result set to retrieve the value from + * @param pIndex the index to retrieve the result from + * @return the original data value + */ + @Nullable + protected VALUE retrieveSerialValue(Class pValueType, ResultSet pResultSet, int pIndex) + { + final _SupportedSerialization serialization = _getSerializationForType(pValueType); + return serialization.retrieveConvertedResult(pResultSet, pIndex); + } + /** * Resolves a {@link _SupportedSerialization} for a specific data type. * Throws a {@link OJDatabaseException} if the data type is not supported. @@ -237,4 +252,23 @@ private interface _ResultRetriever */ SERIAL retrieveResult(ResultSet pResultSet, int pIndex) throws SQLException; } + + /** + * Null serial value. + */ + private static class _NullSerialValue implements ISerialValue + { + @Override + public void applyToStatement(PreparedStatement pStatement, int pIndex) + { + try + { + pStatement.setObject(pIndex, null); + } + catch (SQLException pE) + { + throw new OJDatabaseException("Unable to apply parameter to statement!", pE); + } + } + } } diff --git a/ojcms-sqldatasource/src/main/java/de/adito/ojcms/sql/datasource/util/BeanSQLSerializer.java b/ojcms-sqldatasource/src/main/java/de/adito/ojcms/sql/datasource/util/BeanSQLSerializer.java index d3feb86..f35977c 100644 --- a/ojcms-sqldatasource/src/main/java/de/adito/ojcms/sql/datasource/util/BeanSQLSerializer.java +++ b/ojcms-sqldatasource/src/main/java/de/adito/ojcms/sql/datasource/util/BeanSQLSerializer.java @@ -50,7 +50,7 @@ private ISerialValue _toSerialValue(FieldVa final VALUE value = pTuple.getValue(); if (value == null) - return null; + return NULL_SERIAL_VALUE; if (!(field instanceof ISerializableField)) throw new BeanSerializationException(_notSerializableMessage(field, true)); @@ -70,19 +70,21 @@ private ISerialValue _toSerialValue(FieldVa * @param the data type of the bean field * @return the converted data value */ - @SuppressWarnings("unchecked") private VALUE _fromPersistent(IColumnIdentification pColumn, ResultSet pResultSet, int pIndex) { - final SERIAL serialValue = (SERIAL) super.fromSerial(pColumn, pResultSet, pIndex); - - if (!(pColumn instanceof IBeanFieldTupleBased)) - return (VALUE) serialValue; + if (!(pColumn instanceof IBeanFieldBased)) + //noinspection unchecked + return (VALUE) super.fromSerial(pColumn, pResultSet, pIndex); - final IField field = ((IBeanFieldTupleBased) pColumn).getFieldValueTuple().getField(); + //noinspection unchecked + final IField field = ((IBeanFieldBased) pColumn).getBeanField(); if (!(field instanceof ISerializableField)) throw new BeanSerializationException(_notSerializableMessage(field, false)); - return ((ISerializableField) field).fromPersistent(serialValue); + final ISerializableField serializableField = (ISerializableField) field; + final SERIAL serialValue = retrieveSerialValue(serializableField.getSerialValueType(), pResultSet, pIndex); + + return serializableField.fromPersistent(serialValue); } /**