From 965c1e803fa67806d39e725ec1c640bc933bd830 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Fri, 13 Aug 2021 10:47:14 +0200 Subject: [PATCH 1/3] FIX: mutationDetection = SOURCE works also for special json types --- .../server/type/DefaultTypeManager.java | 13 +- .../server/type/ScalarTypeJsonList.java | 66 ++++---- .../server/type/ScalarTypeJsonMap.java | 116 +++++++++----- .../type/ScalarTypeJsonMapPostgres.java | 22 +-- .../server/type/ScalarTypeJsonSet.java | 68 ++++---- .../server/type/ScalarTypeJsonListTest.java | 12 +- .../java/org/tests/json/TestOldValue.java | 120 ++++++++++++++ .../org/tests/model/json/EBasicOldValue.java | 146 ++++++++++++++++++ 8 files changed, 446 insertions(+), 117 deletions(-) create mode 100644 ebean-core/src/test/java/org/tests/json/TestOldValue.java create mode 100644 ebean-core/src/test/java/org/tests/model/json/EBasicOldValue.java diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 1400b2180..4419f1e1f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -289,7 +289,7 @@ public final class DefaultTypeManager implements TypeManager { @Override public ScalarType getDbMapScalarType() { - return (postgres) ? hstoreType : ScalarTypeJsonMap.typeFor(false, Types.VARCHAR); + return (postgres) ? hstoreType : ScalarTypeJsonMap.typeFor(false, Types.VARCHAR, false); } @Override @@ -312,7 +312,7 @@ public final class DefaultTypeManager implements TypeManager { return arrayTypeSetFactory.typeFor(valueType, nullable); } // fallback to JSON storage in VARCHAR column - return new ScalarTypeJsonSet.Varchar(getDocType(valueType), nullable); + return new ScalarTypeJsonSet.Varchar(getDocType(valueType), nullable, false); // TODO: keepSource for @DbArray? } private ScalarType getArrayScalarTypeList(Type valueType, boolean nullable) { @@ -323,7 +323,7 @@ public final class DefaultTypeManager implements TypeManager { return arrayTypeListFactory.typeFor(valueType, nullable); } // fallback to JSON storage in VARCHAR column - return new ScalarTypeJsonList.Varchar(getDocType(valueType), nullable); + return new ScalarTypeJsonList.Varchar(getDocType(valueType), nullable, false); // TODO: keepSource for @DbArray? } private Class> asEnumClass(Type valueType) { @@ -340,10 +340,11 @@ public final class DefaultTypeManager implements TypeManager { Type genericType = prop.getGenericType(); boolean hasJacksonAnnotations = objectMapperPresent && checkJacksonAnnotations(prop); + boolean keepSource = prop.getMutationDetection() == MutationDetection.SOURCE; if (type.equals(List.class)) { DocPropertyType docType = getDocType(genericType); if (!hasJacksonAnnotations && isValueTypeSimple(genericType)) { - return ScalarTypeJsonList.typeFor(postgres, dbType, docType, prop.isNullable()); + return ScalarTypeJsonList.typeFor(postgres, dbType, docType, prop.isNullable(), keepSource); } else { return createJsonObjectMapperType(prop, dbType, docType); } @@ -351,14 +352,14 @@ public final class DefaultTypeManager implements TypeManager { if (type.equals(Set.class)) { DocPropertyType docType = getDocType(genericType); if (!hasJacksonAnnotations && isValueTypeSimple(genericType)) { - return ScalarTypeJsonSet.typeFor(postgres, dbType, docType, prop.isNullable()); + return ScalarTypeJsonSet.typeFor(postgres, dbType, docType, prop.isNullable(), keepSource); } else { return createJsonObjectMapperType(prop, dbType, docType); } } if (type.equals(Map.class)) { if (!hasJacksonAnnotations && isMapValueTypeObject(genericType)) { - return ScalarTypeJsonMap.typeFor(postgres, dbType); + return ScalarTypeJsonMap.typeFor(postgres, dbType, keepSource); } else { return createJsonObjectMapperType(prop, dbType, DocPropertyType.OBJECT); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java index a196085a6..7ca361405 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java @@ -24,24 +24,24 @@ public class ScalarTypeJsonList { /** * Return the appropriate ScalarType based requested dbType and if Postgres. */ - public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docType, boolean nullable) { + public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docType, boolean nullable, boolean keepSource) { if (postgres) { switch (dbType) { case DbPlatformType.JSONB: - return new ScalarTypeJsonList.JsonB(docType, nullable); + return new ScalarTypeJsonList.JsonB(docType, nullable, keepSource); case DbPlatformType.JSON: - return new ScalarTypeJsonList.Json(docType, nullable); + return new ScalarTypeJsonList.Json(docType, nullable, keepSource); } } - return new ScalarTypeJsonList.Varchar(docType, nullable); + return new ScalarTypeJsonList.Varchar(docType, nullable, keepSource); } /** * List mapped to DB VARCHAR. */ public static class Varchar extends ScalarTypeJsonList.Base { - public Varchar(DocPropertyType docType, boolean nullable) { - super(Types.VARCHAR, docType, nullable); + public Varchar(DocPropertyType docType, boolean nullable, boolean keepSource) { + super(Types.VARCHAR, docType, nullable, keepSource); } } @@ -49,8 +49,8 @@ public class ScalarTypeJsonList { * List mapped to Postgres JSON. */ private static class Json extends ScalarTypeJsonList.PgBase { - public Json(DocPropertyType docType, boolean nullable) { - super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docType, nullable); + public Json(DocPropertyType docType, boolean nullable, boolean keepSource) { + super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docType, nullable, keepSource); } } @@ -58,8 +58,8 @@ public class ScalarTypeJsonList { * List mapped to Postgres JSONB. */ private static class JsonB extends ScalarTypeJsonList.PgBase { - public JsonB(DocPropertyType docType, boolean nullable) { - super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docType, nullable); + public JsonB(DocPropertyType docType, boolean nullable, boolean keepSource) { + super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docType, nullable, keepSource); } } @@ -68,14 +68,24 @@ public class ScalarTypeJsonList { */ @SuppressWarnings("rawtypes") private abstract static class Base extends ScalarTypeJsonCollection { + private final boolean keepSource; - public Base(int dbType, DocPropertyType docType, boolean nullable) { + public Base(int dbType, DocPropertyType docType, boolean nullable, boolean keepSource) { super(List.class, dbType, docType, nullable); + this.keepSource = keepSource; + } + + @Override + public boolean isJsonMapper() { + return keepSource; } @Override public List read(DataReader reader) throws SQLException { String json = reader.getString(); + if (isJsonMapper()) { + reader.pushJson(json); + } try { // parse JSON into modifyAware list return EJson.parseList(json, true); @@ -85,17 +95,15 @@ public class ScalarTypeJsonList { } @Override - public void bind(DataBinder binder, List value) throws SQLException { + public final void bind(DataBinder binder, List value) throws SQLException { + String rawJson = isJsonMapper() ? binder.popJson() : null; + if (rawJson == null && value != null) { + rawJson = formatValue(value); + } if (value == null) { bindNull(binder); - } else if (value.isEmpty()) { - binder.setString("[]"); } else { - try { - binder.setString(EJson.write(value)); - } catch (IOException e) { - throw new SQLException("Failed to format List into JSON content", e); - } + bindRawJson(binder, rawJson); } } @@ -107,9 +115,16 @@ public class ScalarTypeJsonList { binder.setString("[]"); } } + + protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException { + binder.setString(rawJson); + } @Override public String formatValue(List value) { + if (value.isEmpty()) { + return "[]"; + } try { return EJson.write(value); } catch (IOException e) { @@ -144,19 +159,14 @@ public class ScalarTypeJsonList { final String pgType; - PgBase(int jdbcType, String pgType, DocPropertyType docType, boolean nullable) { - super(jdbcType, docType, nullable); + PgBase(int jdbcType, String pgType, DocPropertyType docType, boolean nullable, boolean keepSource) { + super(jdbcType, docType, nullable, keepSource); this.pgType = pgType; } - @SuppressWarnings("rawtypes") @Override - public void bind(DataBinder binder, List value) throws SQLException { - if (value == null) { - bindNull(binder); - } else { - binder.setObject(PostgresHelper.asObject(pgType, formatValue(value))); - } + protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException { + binder.setObject(PostgresHelper.asObject(pgType, rawJson)); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java index 6dd4f900b..507e7012b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.sql.Types; @@ -26,27 +27,21 @@ import java.util.Map; @SuppressWarnings("rawtypes") public abstract class ScalarTypeJsonMap extends ScalarTypeBase { - private static final ScalarTypeJsonMap CLOB = new ScalarTypeJsonMap.Clob(); - private static final ScalarTypeJsonMap BLOB = new ScalarTypeJsonMap.Blob(); - private static final ScalarTypeJsonMap VARCHAR = new ScalarTypeJsonMap.Varchar(); - private static final ScalarTypeJsonMap JSON = new ScalarTypeJsonMapPostgres.JSON(); - private static final ScalarTypeJsonMap JSONB = new ScalarTypeJsonMapPostgres.JSONB(); - /** * Return the ScalarType for the requested dbType and postgres. */ - public static ScalarTypeJsonMap typeFor(boolean postgres, int dbType) { + public static ScalarTypeJsonMap typeFor(boolean postgres, int dbType, boolean keepSource) { switch (dbType) { case Types.VARCHAR: - return VARCHAR; + return new ScalarTypeJsonMap.Varchar(keepSource); case Types.BLOB: - return BLOB; + return new ScalarTypeJsonMap.Blob(keepSource); case Types.CLOB: - return CLOB; + return new ScalarTypeJsonMap.Clob(keepSource); case DbPlatformType.JSONB: - return postgres ? JSONB : CLOB; + return postgres ? new ScalarTypeJsonMapPostgres.JSONB(keepSource) : new ScalarTypeJsonMap.Clob(keepSource); case DbPlatformType.JSON: - return postgres ? JSON : CLOB; + return postgres ? new ScalarTypeJsonMapPostgres.JSON(keepSource) : new ScalarTypeJsonMap.Clob(keepSource); default: throw new IllegalStateException("Unknown dbType " + dbType); } @@ -54,41 +49,50 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { public static class Clob extends ScalarTypeJsonMap { - public Clob() { - super(Types.CLOB); + public Clob(boolean keepSource) { + super(Types.CLOB, keepSource); } @Override - public Map read(DataReader reader) throws SQLException { - String content = reader.getStringFromStream(); - if (content == null) { - return null; - } - return parse(content); + protected String readJson(DataReader reader) throws SQLException { + return reader.getStringFromStream(); } } public static class Varchar extends ScalarTypeJsonMap { - public Varchar() { - super(Types.VARCHAR); + public Varchar(boolean keepSource) { + super(Types.VARCHAR, keepSource); } } public static class Blob extends ScalarTypeJsonMap { - public Blob() { - super(Types.BLOB); + public Blob(boolean keepSource) { + super(Types.BLOB, keepSource); } @Override public Map read(DataReader reader) throws SQLException { InputStream is = reader.getBinaryStream(); if (is == null) { + if (isJsonMapper()) { + reader.pushJson(null); + } return null; } try { - try (InputStreamReader inputStreamReader = new InputStreamReader(is)) { - return parse(inputStreamReader); + if (isJsonMapper()) { + StringWriter rawJson = new StringWriter(); + try (InputStreamReader inputStreamReader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + inputStreamReader.transferTo(rawJson); + } + reader.pushJson(rawJson.toString()); + return parse(rawJson.toString()); + + } else { + try (InputStreamReader inputStreamReader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + return parse(inputStreamReader); + } } } catch (IOException e) { throw new SQLException("Error reading Blob stream from DB", e); @@ -96,18 +100,22 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { } @Override - public void bind(DataBinder binder, Map value) throws SQLException { - if (value == null) { - binder.setNull(Types.BLOB); - } else { - String rawJson = formatValue(value); - binder.setBytes(rawJson.getBytes(StandardCharsets.UTF_8)); - } + protected void bindNull(DataBinder binder) throws SQLException { + binder.setNull(Types.BLOB); } + + @Override + protected void bindJson(DataBinder binder, String rawJson) throws SQLException { + binder.setBytes(rawJson.getBytes(StandardCharsets.UTF_8)); + } + } - public ScalarTypeJsonMap(int jdbcType) { + private final boolean keepSource; + + public ScalarTypeJsonMap(int jdbcType, boolean keepSource) { super(Map.class, false, jdbcType); + this.keepSource = keepSource; } /** @@ -125,26 +133,56 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { public boolean isDirty(Object value) { return TypeJsonManager.checkIsDirty(value); } + + + + @Override + public boolean isJsonMapper() { + return keepSource; + } + @Override public Map read(DataReader reader) throws SQLException { - String rawJson = reader.getString(); + String rawJson = readJson(reader); + if (isJsonMapper()) { + reader.pushJson(rawJson); + } + if (rawJson == null) { return null; } + return parse(rawJson); } + + protected String readJson(DataReader reader) throws SQLException { + return reader.getString(); + } + @Override - public void bind(DataBinder binder, Map value) throws SQLException { + public final void bind(DataBinder binder, Map value) throws SQLException { + String rawJson = isJsonMapper() ? binder.popJson() : null; + if (rawJson == null && value != null) { + rawJson = formatValue(value); + } + if (value == null) { - binder.setNull(Types.VARCHAR); + bindNull(binder); } else { - String rawJson = formatValue(value); - binder.setString(rawJson); + bindJson(binder, rawJson); } } + + protected void bindNull(DataBinder binder) throws SQLException { + binder.setNull(Types.VARCHAR); + } + protected void bindJson(DataBinder binder, String rawJson) throws SQLException { + binder.setString(rawJson); + } + @Override public Object toJdbcType(Object value) { return value; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java index 87eecc5f9..46a585ebc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java @@ -4,7 +4,6 @@ import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.core.type.DataBinder; import java.sql.SQLException; -import java.util.Map; /** * Support for the Postgres DB types JSON and JSONB. @@ -13,15 +12,18 @@ public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap { final String postgresType; - ScalarTypeJsonMapPostgres(int jdbcType, String postgresType) { - super(jdbcType); + ScalarTypeJsonMapPostgres(int jdbcType, String postgresType, boolean keepSource) { + super(jdbcType, keepSource); this.postgresType = postgresType; } - @SuppressWarnings("rawtypes") @Override - public void bind(DataBinder binder, Map value) throws SQLException { - String rawJson = (value == null) ? null : formatValue(value); + protected void bindNull(DataBinder binder) throws SQLException { + binder.setObject(PostgresHelper.asObject(postgresType, null)); + } + + @Override + protected void bindJson(DataBinder binder, String rawJson) throws SQLException { binder.setObject(PostgresHelper.asObject(postgresType, rawJson)); } @@ -30,8 +32,8 @@ public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap { */ public static class JSON extends ScalarTypeJsonMapPostgres { - public JSON() { - super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE); + public JSON(boolean keepSource) { + super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, keepSource); } } @@ -40,8 +42,8 @@ public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap { */ public static class JSONB extends ScalarTypeJsonMapPostgres { - public JSONB() { - super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE); + public JSONB(boolean keepSource) { + super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, keepSource); } } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java index d95adb572..7e728b0d3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java @@ -26,24 +26,24 @@ public class ScalarTypeJsonSet { /** * Return the appropriate ScalarType for the requested dbType and Postgres. */ - public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docPropertyType, boolean nullable) { + public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { if (postgres) { switch (dbType) { case DbPlatformType.JSONB: - return new ScalarTypeJsonSet.JsonB(docPropertyType, nullable); + return new ScalarTypeJsonSet.JsonB(docPropertyType, nullable, keepSource); case DbPlatformType.JSON: - return new ScalarTypeJsonSet.Json(docPropertyType, nullable); + return new ScalarTypeJsonSet.Json(docPropertyType, nullable, keepSource); } } - return new ScalarTypeJsonSet.Varchar(docPropertyType, nullable); + return new ScalarTypeJsonSet.Varchar(docPropertyType, nullable, keepSource); } /** * List mapped to DB VARCHAR. */ public static class Varchar extends ScalarTypeJsonSet.Base { - public Varchar(DocPropertyType docPropertyType, boolean nullable) { - super(Types.VARCHAR, docPropertyType, nullable); + public Varchar(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { + super(Types.VARCHAR, docPropertyType, nullable, keepSource); } } @@ -51,8 +51,8 @@ public class ScalarTypeJsonSet { * List mapped to Postgres JSON. */ private static class Json extends ScalarTypeJsonSet.PgBase { - public Json(DocPropertyType docPropertyType, boolean nullable) { - super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docPropertyType, nullable); + public Json(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { + super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docPropertyType, nullable, keepSource); } } @@ -60,8 +60,8 @@ public class ScalarTypeJsonSet { * List mapped to Postgres JSONB. */ private static class JsonB extends ScalarTypeJsonSet.PgBase { - public JsonB(DocPropertyType docPropertyType, boolean nullable) { - super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docPropertyType, nullable); + public JsonB(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { + super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docPropertyType, nullable, keepSource); } } @@ -71,13 +71,25 @@ public class ScalarTypeJsonSet { @SuppressWarnings("rawtypes") private abstract static class Base extends ScalarTypeJsonCollection { - public Base(int dbType, DocPropertyType docPropertyType, boolean nullable) { + private boolean keepSource; + + public Base(int dbType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { super(Set.class, dbType, docPropertyType, nullable); + this.keepSource = keepSource; } + + @Override + public boolean isJsonMapper() { + return keepSource; + } + @Override public Set read(DataReader reader) throws SQLException { String json = reader.getString(); + if (isJsonMapper()) { + reader.pushJson(json); + } try { // parse JSON into modifyAware list return EJson.parseSet(json, true); @@ -87,17 +99,15 @@ public class ScalarTypeJsonSet { } @Override - public void bind(DataBinder binder, Set value) throws SQLException { + public final void bind(DataBinder binder, Set value) throws SQLException { + String rawJson = isJsonMapper() ? binder.popJson() : null; + if (rawJson == null && value != null) { + rawJson = formatValue(value); + } if (value == null) { bindNull(binder); - } else if (value.isEmpty()) { - binder.setString("[]"); } else { - try { - binder.setString(EJson.write(value)); - } catch (IOException e) { - throw new SQLException("Failed to format Set into JSON content", e); - } + bindRawJson(binder, rawJson); } } @@ -110,8 +120,15 @@ public class ScalarTypeJsonSet { } } + protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException { + binder.setString(rawJson); + } + @Override public String formatValue(Set value) { + if (value.isEmpty()) { + return "[]"; + } try { return EJson.write(value); } catch (IOException e) { @@ -151,19 +168,14 @@ public class ScalarTypeJsonSet { final String pgType; - PgBase(int jdbcType, String pgType, DocPropertyType docPropertyType, boolean nullable) { - super(jdbcType, docPropertyType, nullable); + PgBase(int jdbcType, String pgType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { + super(jdbcType, docPropertyType, nullable, keepSource); this.pgType = pgType; } - @SuppressWarnings("rawtypes") @Override - public void bind(DataBinder binder, Set value) throws SQLException { - if (value == null) { - bindNull(binder); - } else { - binder.setObject(PostgresHelper.asObject(pgType, formatValue(value))); - } + protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException { + binder.setObject(PostgresHelper.asObject(pgType, rawJson)); } @Override diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeJsonListTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeJsonListTest.java index ff329efac..43ed867ba 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeJsonListTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeJsonListTest.java @@ -11,19 +11,19 @@ public class ScalarTypeJsonListTest extends BasePlatformArrayTypeFactoryTest { @Test public void typeFor_expect_nullToEmpty_when_postgresNonNull() throws SQLException { - assertBindNullTo_PGObjectEmpty(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONB, DocPropertyType.OBJECT, false)); - assertBindNullTo_PGObjectEmpty(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSON, DocPropertyType.OBJECT, false)); + assertBindNullTo_PGObjectEmpty(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONB, DocPropertyType.OBJECT, false, false)); + assertBindNullTo_PGObjectEmpty(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSON, DocPropertyType.OBJECT, false, false)); - assertBindNullTo_EmptyString(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONVarchar, DocPropertyType.OBJECT, false)); + assertBindNullTo_EmptyString(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONVarchar, DocPropertyType.OBJECT, false, false)); } @Test public void typeFor_expect_nullToNull_when_nullable() throws SQLException { - assertBindNullTo_PGObjectNull(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONB, DocPropertyType.OBJECT, true)); - assertBindNullTo_PGObjectNull(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSON, DocPropertyType.OBJECT, true)); + assertBindNullTo_PGObjectNull(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONB, DocPropertyType.OBJECT, true, false)); + assertBindNullTo_PGObjectNull(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSON, DocPropertyType.OBJECT, true, false)); - assertBindNullTo_Null(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONVarchar, DocPropertyType.OBJECT, true)); + assertBindNullTo_Null(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONVarchar, DocPropertyType.OBJECT, true, false)); } } diff --git a/ebean-core/src/test/java/org/tests/json/TestOldValue.java b/ebean-core/src/test/java/org/tests/json/TestOldValue.java new file mode 100644 index 000000000..dec8a9f05 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/json/TestOldValue.java @@ -0,0 +1,120 @@ +package org.tests.json; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebean.ValuePair; +import io.ebean.annotation.ForPlatform; +import io.ebean.annotation.Platform; +import io.ebean.text.TextException; + +import org.assertj.core.api.SoftAssertions; +import org.ebeantest.LoggedSqlCollector; +import org.junit.Ignore; +import org.junit.Test; +import org.tests.model.json.EBasicJsonList; +import org.tests.model.json.EBasicOldValue; +import org.tests.model.json.PlainBean; + +import javax.persistence.PersistenceException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class TestOldValue extends BaseTestCase { + + + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void testDbJsonOldValue() throws Exception { + EBasicOldValue bean = new EBasicOldValue(); + + bean.getStringList().add("sl1"); + bean.getStringSet().add("ss1"); + bean.getObjectMap().put("sk1","sm1"); + bean.getLongList().add(1L); + bean.getLongSet().add(1001L); + bean.getLongMap().put("lk1",2001L); + bean.getIntList().add(2); + bean.getIntSet().add(1002); + bean.getIntMap().put("ik1",2002); + + DB.save(bean); + bean = DB.find(EBasicOldValue.class, bean.getId()); + + bean.getStringList().add("sl2"); + bean.getStringSet().add("ss2"); + bean.getObjectMap().put("sk2","sm2"); + bean.getLongList().add(5L); + bean.getLongSet().add(1005L); + bean.getLongMap().put("lk2",2005L); + bean.getIntList().add(6); + bean.getIntSet().add(1006); + bean.getIntMap().put("ik2",2006); + + + Map dirty = DB.getBeanState(bean).getDirtyValues(); + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(dirty).hasSize(9); + + softly.assertThat((List)dirty.get("stringList").getOldValue()).containsExactly("sl1"); + softly.assertThat((List)dirty.get("stringList").getNewValue()).containsExactly("sl1", "sl2"); + softly.assertThat((List)dirty.get("longList").getOldValue()).containsExactly(1L); + softly.assertThat((List)dirty.get("longList").getNewValue()).containsExactly(1L, 5L); + softly.assertThat((List)dirty.get("intList").getOldValue()).containsExactly(2); + softly.assertThat((List)dirty.get("intList").getNewValue()).containsExactly(2, 6); + + softly.assertThat((Set)dirty.get("stringSet").getOldValue()).containsExactly("ss1"); + softly.assertThat((Set)dirty.get("stringSet").getNewValue()).containsExactly("ss1", "ss2"); + softly.assertThat((Set)dirty.get("longSet").getOldValue()).containsExactly(1001L); + softly.assertThat((Set)dirty.get("longSet").getNewValue()).containsExactly(1001L, 1005L); + softly.assertThat((Set)dirty.get("intSet").getOldValue()).containsExactly(1002); + softly.assertThat((Set)dirty.get("intSet").getNewValue()).containsExactly(1002, 1006); + + softly.assertThat((Map)dirty.get("objectMap").getOldValue()).containsEntry("sk1","sm1").hasSize(1); + softly.assertThat((Map)dirty.get("objectMap").getNewValue()).containsEntry("sk1","sm1").containsEntry("sk2","sm2").hasSize(2); + softly.assertThat((Map)dirty.get("longMap").getOldValue()).containsEntry("lk1",2001L).hasSize(1); + softly.assertThat((Map)dirty.get("longMap").getNewValue()).containsEntry("lk1",2001L).containsEntry("lk2",2005L).hasSize(2); + softly.assertThat((Map)dirty.get("intMap").getOldValue()).containsEntry("ik1",2002).hasSize(1); + softly.assertThat((Map)dirty.get("intMap").getNewValue()).containsEntry("ik1",2002).containsEntry("ik2",2006).hasSize(2); + + softly.assertAll(); + + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + @Ignore("Old value detection does not work for @DbArray") + public void testDbArrayOldValue() throws Exception { + EBasicOldValue bean = new EBasicOldValue(); + + bean.getStringArr().add("sa1"); + + + DB.save(bean); + bean = DB.find(EBasicOldValue.class, bean.getId()); + + bean.getStringArr().add("sa2"); + + + + Map dirty = DB.getBeanState(bean).getDirtyValues(); + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(dirty).hasSize(1); + + softly.assertThat((List)dirty.get("stringArr").getOldValue()).containsExactly("sa1"); + softly.assertThat((List)dirty.get("stringArr").getNewValue()).containsExactly("sa1", "sa2"); + + softly.assertAll(); + + } +} diff --git a/ebean-core/src/test/java/org/tests/model/json/EBasicOldValue.java b/ebean-core/src/test/java/org/tests/model/json/EBasicOldValue.java new file mode 100644 index 000000000..27a086ad5 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/json/EBasicOldValue.java @@ -0,0 +1,146 @@ +package org.tests.model.json; + +import io.ebean.annotation.DbArray; +import io.ebean.annotation.DbJson; + +import javax.persistence.Entity; +import javax.persistence.Id; +import java.util.*; + +import static io.ebean.annotation.MutationDetection.SOURCE; + +@Entity +public class EBasicOldValue { + + @Id + Long id; + + String name; + + @DbJson(mutationDetection = SOURCE) + Set stringSet = new LinkedHashSet<>(); + + @DbJson(mutationDetection = SOURCE) + Set longSet = new LinkedHashSet<>(); + + @DbJson(mutationDetection = SOURCE) + Set intSet = new LinkedHashSet<>(); + + @DbJson(mutationDetection = SOURCE) + List stringList = new ArrayList<>(); + + @DbJson(mutationDetection = SOURCE) + List longList = new ArrayList<>(); + + @DbJson(mutationDetection = SOURCE) + List intList = new ArrayList<>(); + + @DbJson(mutationDetection = SOURCE) + Map objectMap = new LinkedHashMap<>(); + + @DbJson(mutationDetection = SOURCE) + Map longMap = new LinkedHashMap<>(); + + @DbJson(mutationDetection = SOURCE) + Map intMap = new LinkedHashMap<>(); + + @DbArray() + List stringArr = new ArrayList<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getStringSet() { + return stringSet; + } + + public void setStringSet(Set stringSet) { + this.stringSet = stringSet; + } + + public Set getLongSet() { + return longSet; + } + + public void setLongSet(Set longSet) { + this.longSet = longSet; + } + + public Set getIntSet() { + return intSet; + } + + public void setIntSet(Set intSet) { + this.intSet = intSet; + } + + public List getStringList() { + return stringList; + } + + public void setStringList(List stringList) { + this.stringList = stringList; + } + + public List getLongList() { + return longList; + } + + public void setLongList(List longList) { + this.longList = longList; + } + + public List getIntList() { + return intList; + } + + public void setIntList(List intList) { + this.intList = intList; + } + + public Map getObjectMap() { + return objectMap; + } + + public void setObjectMap(Map objectMap) { + this.objectMap = objectMap; + } + + public Map getLongMap() { + return longMap; + } + + public void setLongMap(Map longMap) { + this.longMap = longMap; + } + + public Map getIntMap() { + return intMap; + } + + public void setIntMap(Map intMap) { + this.intMap = intMap; + } + + public List getStringArr() { + return stringArr; + } + + public void setStringArr(List stringArr) { + this.stringArr = stringArr; + } + +} From 6b263919d5ccd5c8f552d3268bbb01eb9ea84e65 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Fri, 13 Aug 2021 11:19:24 +0200 Subject: [PATCH 2/3] handle also JsonNode --- .../server/type/DefaultTypeManager.java | 2 +- .../java/org/tests/json/TestOldValue.java | 31 +++++++------------ .../org/tests/model/json/EBasicOldValue.java | 15 +++++++++ 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 4419f1e1f..6e1e1f0d7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -364,7 +364,7 @@ public final class DefaultTypeManager implements TypeManager { return createJsonObjectMapperType(prop, dbType, DocPropertyType.OBJECT); } } - if (objectMapperPresent) { + if (objectMapperPresent && prop.getMutationDetection() == MutationDetection.DEFAULT) { if (type.equals(JsonNode.class)) { switch (dbType) { case Types.VARCHAR: diff --git a/ebean-core/src/test/java/org/tests/json/TestOldValue.java b/ebean-core/src/test/java/org/tests/json/TestOldValue.java index dec8a9f05..f7bd391e5 100644 --- a/ebean-core/src/test/java/org/tests/json/TestOldValue.java +++ b/ebean-core/src/test/java/org/tests/json/TestOldValue.java @@ -3,32 +3,19 @@ package org.tests.json; import io.ebean.BaseTestCase; import io.ebean.DB; import io.ebean.ValuePair; -import io.ebean.annotation.ForPlatform; -import io.ebean.annotation.Platform; -import io.ebean.text.TextException; import org.assertj.core.api.SoftAssertions; -import org.ebeantest.LoggedSqlCollector; import org.junit.Ignore; import org.junit.Test; -import org.tests.model.json.EBasicJsonList; import org.tests.model.json.EBasicOldValue; -import org.tests.model.json.PlainBean; -import javax.persistence.PersistenceException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashSet; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + import java.util.List; import java.util.Map; import java.util.Set; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - public class TestOldValue extends BaseTestCase { @@ -37,6 +24,7 @@ public class TestOldValue extends BaseTestCase { @Test public void testDbJsonOldValue() throws Exception { EBasicOldValue bean = new EBasicOldValue(); + JsonNodeFactory jnf = new JsonNodeFactory(false); bean.getStringList().add("sl1"); bean.getStringSet().add("ss1"); @@ -47,7 +35,9 @@ public class TestOldValue extends BaseTestCase { bean.getIntList().add(2); bean.getIntSet().add(1002); bean.getIntMap().put("ik1",2002); - + + bean.setJsonNode(jnf.arrayNode().add("Foo")); + DB.save(bean); bean = DB.find(EBasicOldValue.class, bean.getId()); @@ -60,11 +50,11 @@ public class TestOldValue extends BaseTestCase { bean.getIntList().add(6); bean.getIntSet().add(1006); bean.getIntMap().put("ik2",2006); - + ((ArrayNode)bean.getJsonNode()).add("Bar"); Map dirty = DB.getBeanState(bean).getDirtyValues(); SoftAssertions softly = new SoftAssertions(); - softly.assertThat(dirty).hasSize(9); + softly.assertThat(dirty).hasSize(10); softly.assertThat((List)dirty.get("stringList").getOldValue()).containsExactly("sl1"); softly.assertThat((List)dirty.get("stringList").getNewValue()).containsExactly("sl1", "sl2"); @@ -87,6 +77,9 @@ public class TestOldValue extends BaseTestCase { softly.assertThat((Map)dirty.get("intMap").getOldValue()).containsEntry("ik1",2002).hasSize(1); softly.assertThat((Map)dirty.get("intMap").getNewValue()).containsEntry("ik1",2002).containsEntry("ik2",2006).hasSize(2); + softly.assertThat((ArrayNode)dirty.get("jsonNode").getOldValue()).hasToString("[\"Foo\"]"); + softly.assertThat((ArrayNode)dirty.get("jsonNode").getNewValue()).hasToString("[\"Foo\",\"Bar\"]"); + softly.assertAll(); } diff --git a/ebean-core/src/test/java/org/tests/model/json/EBasicOldValue.java b/ebean-core/src/test/java/org/tests/model/json/EBasicOldValue.java index 27a086ad5..37e805412 100644 --- a/ebean-core/src/test/java/org/tests/model/json/EBasicOldValue.java +++ b/ebean-core/src/test/java/org/tests/model/json/EBasicOldValue.java @@ -5,6 +5,9 @@ import io.ebean.annotation.DbJson; import javax.persistence.Entity; import javax.persistence.Id; + +import com.fasterxml.jackson.databind.JsonNode; + import java.util.*; import static io.ebean.annotation.MutationDetection.SOURCE; @@ -44,9 +47,13 @@ public class EBasicOldValue { @DbJson(mutationDetection = SOURCE) Map intMap = new LinkedHashMap<>(); + @DbJson(mutationDetection = SOURCE) + JsonNode jsonNode; + @DbArray() List stringArr = new ArrayList<>(); + public Long getId() { return id; } @@ -135,6 +142,14 @@ public class EBasicOldValue { this.intMap = intMap; } + public JsonNode getJsonNode() { + return jsonNode; + } + + public void setJsonNode(JsonNode jsonNode) { + this.jsonNode = jsonNode; + } + public List getStringArr() { return stringArr; } From 1297324fa9b4e778a9b0f0e8305feda3c06df875 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Sun, 15 Aug 2021 13:47:46 +1200 Subject: [PATCH 3/3] Fix where using JDK 10 Reader.transferTo() method plus add final to methods and classes --- .../server/type/ScalarTypeJsonList.java | 46 ++++---- .../server/type/ScalarTypeJsonMap.java | 111 ++++++++---------- .../type/ScalarTypeJsonMapPostgres.java | 18 +-- .../type/ScalarTypeJsonObjectMapper.java | 26 ++-- .../server/type/ScalarTypeJsonSet.java | 46 ++++---- 5 files changed, 118 insertions(+), 129 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java index 7ca361405..699c7ad0f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java @@ -19,12 +19,12 @@ import java.util.List; /** * Types for mapping List in JSON format to DB types VARCHAR, JSON and JSONB. */ -public class ScalarTypeJsonList { +class ScalarTypeJsonList { /** * Return the appropriate ScalarType based requested dbType and if Postgres. */ - public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docType, boolean nullable, boolean keepSource) { + static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docType, boolean nullable, boolean keepSource) { if (postgres) { switch (dbType) { case DbPlatformType.JSONB: @@ -39,8 +39,8 @@ public class ScalarTypeJsonList { /** * List mapped to DB VARCHAR. */ - public static class Varchar extends ScalarTypeJsonList.Base { - public Varchar(DocPropertyType docType, boolean nullable, boolean keepSource) { + static final class Varchar extends ScalarTypeJsonList.Base { + Varchar(DocPropertyType docType, boolean nullable, boolean keepSource) { super(Types.VARCHAR, docType, nullable, keepSource); } } @@ -48,8 +48,8 @@ public class ScalarTypeJsonList { /** * List mapped to Postgres JSON. */ - private static class Json extends ScalarTypeJsonList.PgBase { - public Json(DocPropertyType docType, boolean nullable, boolean keepSource) { + private final static class Json extends ScalarTypeJsonList.PgBase { + Json(DocPropertyType docType, boolean nullable, boolean keepSource) { super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docType, nullable, keepSource); } } @@ -57,8 +57,8 @@ public class ScalarTypeJsonList { /** * List mapped to Postgres JSONB. */ - private static class JsonB extends ScalarTypeJsonList.PgBase { - public JsonB(DocPropertyType docType, boolean nullable, boolean keepSource) { + private static final class JsonB extends ScalarTypeJsonList.PgBase { + JsonB(DocPropertyType docType, boolean nullable, boolean keepSource) { super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docType, nullable, keepSource); } } @@ -68,22 +68,22 @@ public class ScalarTypeJsonList { */ @SuppressWarnings("rawtypes") private abstract static class Base extends ScalarTypeJsonCollection { - private final boolean keepSource; + final boolean keepSource; - public Base(int dbType, DocPropertyType docType, boolean nullable, boolean keepSource) { + private Base(int dbType, DocPropertyType docType, boolean nullable, boolean keepSource) { super(List.class, dbType, docType, nullable); this.keepSource = keepSource; } - + @Override - public boolean isJsonMapper() { + public final boolean isJsonMapper() { return keepSource; } @Override - public List read(DataReader reader) throws SQLException { + public final List read(DataReader reader) throws SQLException { String json = reader.getString(); - if (isJsonMapper()) { + if (keepSource) { reader.pushJson(json); } try { @@ -96,7 +96,7 @@ public class ScalarTypeJsonList { @Override public final void bind(DataBinder binder, List value) throws SQLException { - String rawJson = isJsonMapper() ? binder.popJson() : null; + String rawJson = keepSource ? binder.popJson() : null; if (rawJson == null && value != null) { rawJson = formatValue(value); } @@ -115,15 +115,15 @@ public class ScalarTypeJsonList { binder.setString("[]"); } } - + protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException { binder.setString(rawJson); } @Override - public String formatValue(List value) { + public final String formatValue(List value) { if (value.isEmpty()) { - return "[]"; + return "[]"; } try { return EJson.write(value); @@ -133,7 +133,7 @@ public class ScalarTypeJsonList { } @Override - public List parse(String value) { + public final List parse(String value) { try { return EJson.parseList(value, false); } catch (IOException e) { @@ -142,12 +142,12 @@ public class ScalarTypeJsonList { } @Override - public List jsonRead(JsonParser parser) throws IOException { + public final List jsonRead(JsonParser parser) throws IOException { return EJson.parseList(parser, parser.getCurrentToken()); } @Override - public void jsonWrite(JsonGenerator writer, List value) throws IOException { + public final void jsonWrite(JsonGenerator writer, List value) throws IOException { EJson.write(value, writer); } } @@ -165,12 +165,12 @@ public class ScalarTypeJsonList { } @Override - protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException { + protected final void bindRawJson(DataBinder binder, String rawJson) throws SQLException { binder.setObject(PostgresHelper.asObject(pgType, rawJson)); } @Override - protected void bindNull(DataBinder binder) throws SQLException { + protected final void bindNull(DataBinder binder) throws SQLException { binder.setObject(PostgresHelper.asObject(pgType, nullable ? null : "[]")); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java index 507e7012b..93ecdab79 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java @@ -9,13 +9,7 @@ import io.ebean.core.type.DocPropertyType; import io.ebean.text.TextException; import io.ebean.text.json.EJson; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.StringWriter; +import java.io.*; import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.sql.Types; @@ -25,12 +19,12 @@ import java.util.Map; * Type which maps Map to various DB types (Clob, Varchar, Blob) in JSON format. */ @SuppressWarnings("rawtypes") -public abstract class ScalarTypeJsonMap extends ScalarTypeBase { +abstract class ScalarTypeJsonMap extends ScalarTypeBase { /** * Return the ScalarType for the requested dbType and postgres. */ - public static ScalarTypeJsonMap typeFor(boolean postgres, int dbType, boolean keepSource) { + static ScalarTypeJsonMap typeFor(boolean postgres, int dbType, boolean keepSource) { switch (dbType) { case Types.VARCHAR: return new ScalarTypeJsonMap.Varchar(keepSource); @@ -47,9 +41,8 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { } } - public static class Clob extends ScalarTypeJsonMap { - - public Clob(boolean keepSource) { + private static final class Clob extends ScalarTypeJsonMap { + Clob(boolean keepSource) { super(Types.CLOB, keepSource); } @@ -59,15 +52,14 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { } } - public static class Varchar extends ScalarTypeJsonMap { - - public Varchar(boolean keepSource) { + private static final class Varchar extends ScalarTypeJsonMap { + Varchar(boolean keepSource) { super(Types.VARCHAR, keepSource); } } - public static class Blob extends ScalarTypeJsonMap { - public Blob(boolean keepSource) { + private static final class Blob extends ScalarTypeJsonMap { + Blob(boolean keepSource) { super(Types.BLOB, keepSource); } @@ -75,23 +67,23 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { public Map read(DataReader reader) throws SQLException { InputStream is = reader.getBinaryStream(); if (is == null) { - if (isJsonMapper()) { + if (keepSource) { reader.pushJson(null); } return null; } try { - if (isJsonMapper()) { - StringWriter rawJson = new StringWriter(); - try (InputStreamReader inputStreamReader = new InputStreamReader(is, StandardCharsets.UTF_8)) { - inputStreamReader.transferTo(rawJson); + if (keepSource) { + StringWriter jsonBuffer = new StringWriter(); + try (InputStreamReader streamReader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + transferTo(streamReader, jsonBuffer); } - reader.pushJson(rawJson.toString()); - return parse(rawJson.toString()); - + String rawJson = jsonBuffer.toString(); + reader.pushJson(rawJson); + return parse(rawJson); } else { - try (InputStreamReader inputStreamReader = new InputStreamReader(is, StandardCharsets.UTF_8)) { - return parse(inputStreamReader); + try (InputStreamReader streamReader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + return parse(streamReader); } } } catch (IOException e) { @@ -99,21 +91,29 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { } } + private static void transferTo(Reader reader, Writer out) throws IOException { + char[] buffer = new char[2048]; + int nRead; + while ((nRead = reader.read(buffer, 0, 2048)) >= 0) { + out.write(buffer, 0, nRead); + } + } + @Override protected void bindNull(DataBinder binder) throws SQLException { binder.setNull(Types.BLOB); } - + @Override protected void bindJson(DataBinder binder, String rawJson) throws SQLException { binder.setBytes(rawJson.getBytes(StandardCharsets.UTF_8)); } - + } - private final boolean keepSource; - - public ScalarTypeJsonMap(int jdbcType, boolean keepSource) { + final boolean keepSource; + + ScalarTypeJsonMap(int jdbcType, boolean keepSource) { super(Map.class, false, jdbcType); this.keepSource = keepSource; } @@ -122,7 +122,7 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { * Map is a mutable type. Use the isDirty() method to check for dirty state. */ @Override - public boolean isMutable() { + public final boolean isMutable() { return true; } @@ -133,48 +133,41 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { public boolean isDirty(Object value) { return TypeJsonManager.checkIsDirty(value); } - - - + @Override - public boolean isJsonMapper() { + public final boolean isJsonMapper() { return keepSource; } - @Override public Map read(DataReader reader) throws SQLException { String rawJson = readJson(reader); - if (isJsonMapper()) { + if (keepSource) { reader.pushJson(rawJson); } - if (rawJson == null) { return null; } - return parse(rawJson); } - protected String readJson(DataReader reader) throws SQLException { return reader.getString(); } - + @Override public final void bind(DataBinder binder, Map value) throws SQLException { - String rawJson = isJsonMapper() ? binder.popJson() : null; + String rawJson = keepSource ? binder.popJson() : null; if (rawJson == null && value != null) { rawJson = formatValue(value); } - if (value == null) { bindNull(binder); } else { bindJson(binder, rawJson); } } - + protected void bindNull(DataBinder binder) throws SQLException { binder.setNull(Types.VARCHAR); } @@ -182,19 +175,19 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { protected void bindJson(DataBinder binder, String rawJson) throws SQLException { binder.setString(rawJson); } - + @Override - public Object toJdbcType(Object value) { + public final Object toJdbcType(Object value) { return value; } @Override - public Map toBeanType(Object value) { + public final Map toBeanType(Object value) { return (Map) value; } @Override - public String formatValue(Map v) { + public final String formatValue(Map v) { try { return EJson.write(v); } catch (IOException e) { @@ -203,7 +196,7 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { } @Override - public Map parse(String value) { + public final Map parse(String value) { try { // return a modify aware map return EJson.parseObject(value, true); @@ -212,7 +205,7 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { } } - public Map parse(Reader reader) { + public final Map parse(Reader reader) { try { // return a modify aware map return EJson.parseObject(reader, true); @@ -222,17 +215,17 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { } @Override - public Map convertFromMillis(long dateTime) { + public final Map convertFromMillis(long dateTime) { throw new RuntimeException("Should never be called"); } @Override - public boolean isDateTimeCapable() { + public final boolean isDateTimeCapable() { return false; } @Override - public Map readData(DataInput dataInput) throws IOException { + public final Map readData(DataInput dataInput) throws IOException { if (!dataInput.readBoolean()) { return null; } else { @@ -241,7 +234,7 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { } @Override - public void writeData(DataOutput dataOutput, Map map) throws IOException { + public final void writeData(DataOutput dataOutput, Map map) throws IOException { if (map == null) { dataOutput.writeBoolean(false); } else { @@ -250,17 +243,17 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { } @Override - public void jsonWrite(JsonGenerator writer, Map value) throws IOException { + public final void jsonWrite(JsonGenerator writer, Map value) throws IOException { EJson.write(value, writer); } @Override - public Map jsonRead(JsonParser parser) throws IOException { + public final Map jsonRead(JsonParser parser) throws IOException { return EJson.parseObject(parser, parser.getCurrentToken()); } @Override - public DocPropertyType getDocType() { + public final DocPropertyType getDocType() { return DocPropertyType.OBJECT; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java index 46a585ebc..ae33fa0e4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java @@ -8,9 +8,9 @@ import java.sql.SQLException; /** * Support for the Postgres DB types JSON and JSONB. */ -public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap { +abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap { - final String postgresType; + private final String postgresType; ScalarTypeJsonMapPostgres(int jdbcType, String postgresType, boolean keepSource) { super(jdbcType, keepSource); @@ -18,21 +18,21 @@ public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap { } @Override - protected void bindNull(DataBinder binder) throws SQLException { + protected final void bindNull(DataBinder binder) throws SQLException { binder.setObject(PostgresHelper.asObject(postgresType, null)); } - + @Override - protected void bindJson(DataBinder binder, String rawJson) throws SQLException { + protected final void bindJson(DataBinder binder, String rawJson) throws SQLException { binder.setObject(PostgresHelper.asObject(postgresType, rawJson)); } /** * ScalarType mapping java Map type to Postgres JSON database type. */ - public static class JSON extends ScalarTypeJsonMapPostgres { + static final class JSON extends ScalarTypeJsonMapPostgres { - public JSON(boolean keepSource) { + JSON(boolean keepSource) { super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, keepSource); } } @@ -40,9 +40,9 @@ public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap { /** * ScalarType mapping java Map type to Postgres JSONB database type. */ - public static class JSONB extends ScalarTypeJsonMapPostgres { + static final class JSONB extends ScalarTypeJsonMapPostgres { - public JSONB(boolean keepSource) { + JSONB(boolean keepSource) { super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, keepSource); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java index 41cb28d49..65cb88cdc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java @@ -46,7 +46,7 @@ class ScalarTypeJsonObjectMapper { /** * No mutation detection on this json property. */ - private static class NoMutationDetection extends Base { + private static final class NoMutationDetection extends Base { NoMutationDetection(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) { super(Object.class, jsonManager, field, dbType, docType); @@ -66,7 +66,7 @@ class ScalarTypeJsonObjectMapper { /** * Supports HASH and SOURCE dirty detection modes. */ - private static class GenericObject extends Base { + private static final class GenericObject extends Base { GenericObject(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) { super(Object.class, jsonManager, field, dbType, docType); @@ -167,20 +167,20 @@ class ScalarTypeJsonObjectMapper { } @Override - public Object toJdbcType(Object value) { + public final Object toJdbcType(Object value) { // no type conversion supported return value; } @Override @SuppressWarnings("unchecked") - public T toBeanType(Object value) { + public final T toBeanType(Object value) { // no type conversion supported return (T) value; } @Override - public String formatValue(T value) { + public final String formatValue(T value) { try { return objectWriter.writeValueAsString(value); } catch (JsonProcessingException e) { @@ -189,7 +189,7 @@ class ScalarTypeJsonObjectMapper { } @Override - public T parse(String value) { + public final T parse(String value) { try { return objectReader.readValue(value, deserType); } catch (IOException e) { @@ -198,32 +198,32 @@ class ScalarTypeJsonObjectMapper { } @Override - public DocPropertyType getDocType() { + public final DocPropertyType getDocType() { return docType; } @Override - public boolean isDateTimeCapable() { + public final boolean isDateTimeCapable() { return false; } @Override - public T convertFromMillis(long dateTime) { + public final T convertFromMillis(long dateTime) { throw new IllegalStateException("Not supported"); } @Override - public T jsonRead(JsonParser parser) throws IOException { + public final T jsonRead(JsonParser parser) throws IOException { return objectReader.readValue(parser, deserType); } @Override - public void jsonWrite(JsonGenerator writer, T value) throws IOException { + public final void jsonWrite(JsonGenerator writer, T value) throws IOException { objectWriter.writeValue(writer, value); } @Override - public T readData(DataInput dataInput) throws IOException { + public final T readData(DataInput dataInput) throws IOException { if (!dataInput.readBoolean()) { return null; } else { @@ -232,7 +232,7 @@ class ScalarTypeJsonObjectMapper { } @Override - public void writeData(DataOutput dataOutput, T value) throws IOException { + public final void writeData(DataOutput dataOutput, T value) throws IOException { if (value == null) { dataOutput.writeBoolean(false); } else { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java index 7e728b0d3..86a39443e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java @@ -21,12 +21,12 @@ import java.util.Set; /** * Types for mapping List in JSON format to DB types VARCHAR, JSON and JSONB. */ -public class ScalarTypeJsonSet { +final class ScalarTypeJsonSet { /** * Return the appropriate ScalarType for the requested dbType and Postgres. */ - public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { + static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { if (postgres) { switch (dbType) { case DbPlatformType.JSONB: @@ -41,7 +41,7 @@ public class ScalarTypeJsonSet { /** * List mapped to DB VARCHAR. */ - public static class Varchar extends ScalarTypeJsonSet.Base { + static final class Varchar extends ScalarTypeJsonSet.Base { public Varchar(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { super(Types.VARCHAR, docPropertyType, nullable, keepSource); } @@ -50,8 +50,8 @@ public class ScalarTypeJsonSet { /** * List mapped to Postgres JSON. */ - private static class Json extends ScalarTypeJsonSet.PgBase { - public Json(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { + private static final class Json extends ScalarTypeJsonSet.PgBase { + private Json(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docPropertyType, nullable, keepSource); } } @@ -59,35 +59,31 @@ public class ScalarTypeJsonSet { /** * List mapped to Postgres JSONB. */ - private static class JsonB extends ScalarTypeJsonSet.PgBase { - public JsonB(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { + private static final class JsonB extends ScalarTypeJsonSet.PgBase { + private JsonB(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docPropertyType, nullable, keepSource); } } - /** - * Base class for List handling. - */ @SuppressWarnings("rawtypes") private abstract static class Base extends ScalarTypeJsonCollection { - private boolean keepSource; - - public Base(int dbType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { + final boolean keepSource; + + private Base(int dbType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { super(Set.class, dbType, docPropertyType, nullable); this.keepSource = keepSource; } - @Override - public boolean isJsonMapper() { + public final boolean isJsonMapper() { return keepSource; } - + @Override - public Set read(DataReader reader) throws SQLException { + public final Set read(DataReader reader) throws SQLException { String json = reader.getString(); - if (isJsonMapper()) { + if (keepSource) { reader.pushJson(json); } try { @@ -100,7 +96,7 @@ public class ScalarTypeJsonSet { @Override public final void bind(DataBinder binder, Set value) throws SQLException { - String rawJson = isJsonMapper() ? binder.popJson() : null; + String rawJson = keepSource ? binder.popJson() : null; if (rawJson == null && value != null) { rawJson = formatValue(value); } @@ -125,7 +121,7 @@ public class ScalarTypeJsonSet { } @Override - public String formatValue(Set value) { + public final String formatValue(Set value) { if (value.isEmpty()) { return "[]"; } @@ -137,7 +133,7 @@ public class ScalarTypeJsonSet { } @Override - public Set parse(String value) { + public final Set parse(String value) { try { return convertList(EJson.parseList(value)); } catch (IOException e) { @@ -146,12 +142,12 @@ public class ScalarTypeJsonSet { } @Override - public Set jsonRead(JsonParser parser) throws IOException { + public final Set jsonRead(JsonParser parser) throws IOException { return convertList(EJson.parseList(parser, parser.getCurrentToken())); } @Override - public void jsonWrite(JsonGenerator writer, Set value) throws IOException { + public final void jsonWrite(JsonGenerator writer, Set value) throws IOException { EJson.write(value, writer); } @@ -174,12 +170,12 @@ public class ScalarTypeJsonSet { } @Override - protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException { + protected final void bindRawJson(DataBinder binder, String rawJson) throws SQLException { binder.setObject(PostgresHelper.asObject(pgType, rawJson)); } @Override - protected void bindNull(DataBinder binder) throws SQLException { + protected final void bindNull(DataBinder binder) throws SQLException { binder.setObject(PostgresHelper.asObject(pgType, nullable ? null : "[]")); } }