Merge branch 'mutationdetection-source-special-json-types' of https://github.com/FOCONIS/ebean into FOCONIS-mutationdetection-source-special-json-types

This commit is contained in:
rbygrave
2021-08-15 12:06:12 +12:00
8 changed files with 455 additions and 118 deletions
@@ -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<? extends Enum<?>> 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,19 +352,19 @@ 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);
}
}
if (objectMapperPresent) {
if (objectMapperPresent && prop.getMutationDetection() == MutationDetection.DEFAULT) {
if (type.equals(JsonNode.class)) {
switch (dbType) {
case Types.VARCHAR:
@@ -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<List> {
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
@@ -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<Map> {
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<Map> {
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<Map> {
}
@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<Map> {
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;
@@ -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);
}
}
}
@@ -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<Set> {
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
@@ -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));
}
}
@@ -0,0 +1,113 @@
package org.tests.json;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebean.ValuePair;
import org.assertj.core.api.SoftAssertions;
import org.junit.Ignore;
import org.junit.Test;
import org.tests.model.json.EBasicOldValue;
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;
public class TestOldValue extends BaseTestCase {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
public void testDbJsonOldValue() throws Exception {
EBasicOldValue bean = new EBasicOldValue();
JsonNodeFactory jnf = new JsonNodeFactory(false);
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);
bean.setJsonNode(jnf.arrayNode().add("Foo"));
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);
((ArrayNode)bean.getJsonNode()).add("Bar");
Map<String, ValuePair> dirty = DB.getBeanState(bean).getDirtyValues();
SoftAssertions softly = new SoftAssertions();
softly.assertThat(dirty).hasSize(10);
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.assertThat((ArrayNode)dirty.get("jsonNode").getOldValue()).hasToString("[\"Foo\"]");
softly.assertThat((ArrayNode)dirty.get("jsonNode").getNewValue()).hasToString("[\"Foo\",\"Bar\"]");
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<String, ValuePair> 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();
}
}
@@ -0,0 +1,161 @@
package org.tests.model.json;
import io.ebean.annotation.DbArray;
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;
@Entity
public class EBasicOldValue {
@Id
Long id;
String name;
@DbJson(mutationDetection = SOURCE)
Set<String> stringSet = new LinkedHashSet<>();
@DbJson(mutationDetection = SOURCE)
Set<Long> longSet = new LinkedHashSet<>();
@DbJson(mutationDetection = SOURCE)
Set<Integer> intSet = new LinkedHashSet<>();
@DbJson(mutationDetection = SOURCE)
List<String> stringList = new ArrayList<>();
@DbJson(mutationDetection = SOURCE)
List<Long> longList = new ArrayList<>();
@DbJson(mutationDetection = SOURCE)
List<Integer> intList = new ArrayList<>();
@DbJson(mutationDetection = SOURCE)
Map<String, Object> objectMap = new LinkedHashMap<>();
@DbJson(mutationDetection = SOURCE)
Map<String, Long> longMap = new LinkedHashMap<>();
@DbJson(mutationDetection = SOURCE)
Map<String, Integer> intMap = new LinkedHashMap<>();
@DbJson(mutationDetection = SOURCE)
JsonNode jsonNode;
@DbArray()
List<String> 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<String> getStringSet() {
return stringSet;
}
public void setStringSet(Set<String> stringSet) {
this.stringSet = stringSet;
}
public Set<Long> getLongSet() {
return longSet;
}
public void setLongSet(Set<Long> longSet) {
this.longSet = longSet;
}
public Set<Integer> getIntSet() {
return intSet;
}
public void setIntSet(Set<Integer> intSet) {
this.intSet = intSet;
}
public List<String> getStringList() {
return stringList;
}
public void setStringList(List<String> stringList) {
this.stringList = stringList;
}
public List<Long> getLongList() {
return longList;
}
public void setLongList(List<Long> longList) {
this.longList = longList;
}
public List<Integer> getIntList() {
return intList;
}
public void setIntList(List<Integer> intList) {
this.intList = intList;
}
public Map<String, Object> getObjectMap() {
return objectMap;
}
public void setObjectMap(Map<String, Object> objectMap) {
this.objectMap = objectMap;
}
public Map<String, Long> getLongMap() {
return longMap;
}
public void setLongMap(Map<String, Long> longMap) {
this.longMap = longMap;
}
public Map<String, Integer> getIntMap() {
return intMap;
}
public void setIntMap(Map<String, Integer> intMap) {
this.intMap = intMap;
}
public JsonNode getJsonNode() {
return jsonNode;
}
public void setJsonNode(JsonNode jsonNode) {
this.jsonNode = jsonNode;
}
public List<String> getStringArr() {
return stringArr;
}
public void setStringArr(List<String> stringArr) {
this.stringArr = stringArr;
}
}