Treat json Jackson collections the same wrt mutation detection

This commit is contained in:
rbygrave
2021-07-30 20:20:03 +12:00
parent d9f7531e81
commit ceea705a76
8 changed files with 41 additions and 181 deletions
@@ -193,12 +193,6 @@ public class DatabaseConfig {
*/
private JsonConfig.Include jsonInclude = JsonConfig.Include.ALL;
/**
* When true then by default DbJson beans are assumed to be dirty.
* I believe we want to change this default to false in the future.
*/
private boolean jsonDirtyByDefault = true;
/**
* The database platform name. Used to imply a DatabasePlatform to use.
*/
@@ -743,26 +737,6 @@ public class DatabaseConfig {
this.jsonInclude = jsonInclude;
}
/**
* Return true if DbJson beans are assumed dirty by default.
* <p>
* That is, when true beans that do not implement ModifyAwareType are by
* default assumed to be dirty and included in updates.
*/
public boolean isJsonDirtyByDefault() {
return jsonDirtyByDefault;
}
/**
* Set to false if we want DbJson beans to not be assumed to be dirty.
* <p>
* That is, when true beans that do not implement ModifyAwareType are by
* default assumed to be dirty and included in updates.
*/
public void setJsonDirtyByDefault(boolean jsonDirtyByDefault) {
this.jsonDirtyByDefault = jsonDirtyByDefault;
}
/**
* Return the name of the Database.
*/
@@ -2935,7 +2909,6 @@ public class DatabaseConfig {
jsonInclude = p.getEnum(JsonConfig.Include.class, "jsonInclude", jsonInclude);
jsonDateTime = p.getEnum(JsonConfig.DateTime.class, "jsonDateTime", jsonDateTime);
jsonDate = p.getEnum(JsonConfig.Date.class, "jsonDate", jsonDate);
jsonDirtyByDefault = p.getBoolean("jsonDirtyByDefault", jsonDirtyByDefault);
runMigration = p.getBoolean("migration.run", runMigration);
ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate);
@@ -134,7 +134,7 @@ public final class DefaultTypeManager implements TypeManager {
this.postgres = isPostgres(config.getDatabasePlatform());
this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null;
this.jsonManager = (objectMapperPresent) ? new TypeJsonManager(postgres, objectMapper, config.isJsonDirtyByDefault()) : null;
this.jsonManager = (objectMapperPresent) ? new TypeJsonManager(postgres, objectMapper) : null;
this.extraTypeFactory = new DefaultTypeFactory(config);
this.arrayTypeListFactory = arrayTypeListFactory(config.getDatabasePlatform());
this.arrayTypeSetFactory = arrayTypeSetFactory(config.getDatabasePlatform());
@@ -556,7 +556,7 @@ public final class DefaultTypeManager implements TypeManager {
// no override or further mapping required
return scalarType;
}
ScalarTypeEnum<?> scalarEnum = (ScalarTypeEnum<?>)scalarType;
ScalarTypeEnum<?> scalarEnum = (ScalarTypeEnum<?>) scalarType;
if (scalarEnum != null && !scalarEnum.isOverrideBy(type)) {
if (type != null && !scalarEnum.isCompatible(type)) {
throw new IllegalStateException("Error mapping Enum type:" + enumType + " It is mapped using 2 different modes when only one is supported (ORDINAL, STRING or an Ebean mapping)");
@@ -673,7 +673,7 @@ public final class DefaultTypeManager implements TypeManager {
private Object initObjectMapper(DatabaseConfig config) {
Object objectMapper = config.getObjectMapper();
if (objectMapper == null) {
objectMapper = new ObjectMapper();
objectMapper = InitObjectMapper.init();
config.setObjectMapper(objectMapper);
}
return objectMapper;
@@ -0,0 +1,22 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Initialise the Jackson ObjectMapper.
*/
class InitObjectMapper {
/**
* Create and return the default ObjectMapper.
*/
static Object init() {
SimpleModule module = new SimpleModule();
module.addAbstractTypeMapping(Set.class, LinkedHashSet.class);
return new ObjectMapper().registerModule(module);
}
}
@@ -13,9 +13,6 @@ import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
import io.ebean.core.type.ScalarType;
import io.ebean.text.TextException;
import io.ebeaninternal.json.ModifyAwareList;
import io.ebeaninternal.json.ModifyAwareMap;
import io.ebeaninternal.json.ModifyAwareSet;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.PersistenceException;
@@ -24,9 +21,6 @@ import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Supports @DbJson properties using Jackson ObjectMapper.
@@ -38,25 +32,14 @@ class ScalarTypeJsonObjectMapper {
*/
static ScalarType<?> createTypeFor(TypeJsonManager jsonManager, DeployBeanProperty prop, int dbType, DocPropertyType docType) {
AnnotatedField field = (AnnotatedField) prop.getJacksonField();
Class<?> type = field.getRawType();
MutationDetection mode = prop.getMutationDetection();
if (mode == MutationDetection.NONE) {
return new NoMutationDetection(jsonManager, field, dbType, type);
return new NoMutationDetection(jsonManager, field, dbType, docType);
} else if (mode != MutationDetection.DEFAULT) {
return new GenericObject(jsonManager, field, dbType, type);
}
if (Set.class.equals(type)) {
return new OmSet(jsonManager, field, dbType, docType);
}
if (List.class.equals(type)) {
return new OmList(jsonManager, field, dbType, docType);
}
if (Map.class.equals(type)) {
return new OmMap(jsonManager, field, dbType);
return new GenericObject(jsonManager, field, dbType, docType);
}
prop.setMutationDetection(MutationDetection.HASH);
return new GenericObject(jsonManager, field, dbType, type);
return new GenericObject(jsonManager, field, dbType, docType);
}
/**
@@ -64,8 +47,8 @@ class ScalarTypeJsonObjectMapper {
*/
private static class NoMutationDetection extends Base<Object> {
NoMutationDetection(TypeJsonManager jsonManager, AnnotatedField field, int dbType, Class<?> rawType) {
super(Object.class, jsonManager, field, dbType, DocPropertyType.OBJECT, rawType);
NoMutationDetection(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
super(Object.class, jsonManager, field, dbType, docType);
}
@Override
@@ -84,8 +67,8 @@ class ScalarTypeJsonObjectMapper {
*/
private static class GenericObject extends Base<Object> {
GenericObject(TypeJsonManager jsonManager, AnnotatedField field, int dbType, Class<?> rawType) {
super(Object.class, jsonManager, field, dbType, DocPropertyType.OBJECT, rawType);
GenericObject(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
super(Object.class, jsonManager, field, dbType, docType);
}
@Override
@@ -128,60 +111,6 @@ class ScalarTypeJsonObjectMapper {
}
}
/**
* Type for Sets wrapping the ObjectMapper Set as a ModifyAwareSet.
*/
@SuppressWarnings("rawtypes")
private static class OmSet extends Base<Set> {
OmSet(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
super(Set.class, jsonManager, field, dbType, docType);
}
@Override
@SuppressWarnings("unchecked")
public Set read(DataReader reader) throws SQLException {
Set value = super.read(reader);
return value == null ? null : new ModifyAwareSet(value);
}
}
/**
* Type for Lists wrapping the ObjectMapper List as a ModifyAwareList.
*/
@SuppressWarnings("rawtypes")
private static class OmList extends Base<List> {
OmList(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
super(List.class, jsonManager, field, dbType, docType);
}
@Override
@SuppressWarnings("unchecked")
public List read(DataReader reader) throws SQLException {
List value = super.read(reader);
return value == null ? null : new ModifyAwareList(value);
}
}
/**
* Type for Map wrapping the ObjectMapper Map as a ModifyAwareMap.
*/
@SuppressWarnings("rawtypes")
private static class OmMap extends Base<Map> {
OmMap(TypeJsonManager jsonManager, AnnotatedField field, int dbType) {
super(Map.class, jsonManager, field, dbType, DocPropertyType.OBJECT);
}
@Override
@SuppressWarnings("unchecked")
public Map read(DataReader reader) throws SQLException {
Map value = super.read(reader);
return value == null ? null : new ModifyAwareMap(value);
}
}
/**
* ScalarType that uses Jackson ObjectMapper to marshall/unmarshall to/from JSON
* and storing them in one of JSON, JSONB, VARCHAR, CLOB or BLOB.
@@ -193,39 +122,22 @@ class ScalarTypeJsonObjectMapper {
protected final JavaType deserType;
protected final String pgType;
private final DocPropertyType docType;
private final TypeJsonManager.DirtyHandler dirtyHandler;
Base(Class<T> cls, TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
this(cls, jsonManager, field, dbType, docType, cls);
}
Base(Class<T> cls, TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType, Class<?> rawType) {
super(cls, false, dbType);
this.objectReader = jsonManager.objectMapper();
this.pgType = jsonManager.postgresType(dbType);
this.docType = docType;
this.dirtyHandler = jsonManager.dirtyHandler(cls, rawType);
final JacksonTypeHelper helper = new JacksonTypeHelper(field, objectReader);
this.deserType = helper.type();
this.objectWriter = helper.objectWriter();
}
/**
* Consider as a mutable type. Use the isDirty() method to check for dirty state.
*/
@Override
public boolean isMutable() {
return true;
}
/**
* Return true if the value should be considered dirty (and included in an update).
*/
@Override
public boolean isDirty(Object value) {
return dirtyHandler.isDirty(value);
}
@Override
public T read(DataReader reader) throws SQLException {
String json = reader.getString();
@@ -2,25 +2,16 @@ package io.ebeaninternal.server.type;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.ebean.ModifyAwareType;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.dbplatform.DbPlatformType;
class TypeJsonManager {
interface DirtyHandler {
boolean isDirty(Object value);
}
private final boolean postgres;
private final ObjectMapper objectMapper;
private final DirtyHandler defaultHandler;
private final DirtyHandler modifyAwareHandler;
TypeJsonManager(boolean postgres, Object objectMapper, boolean defaultDirty) {
TypeJsonManager(boolean postgres, Object objectMapper) {
this.postgres = postgres;
this.objectMapper = (ObjectMapper) objectMapper;
this.defaultHandler = new DefaultHandler(defaultDirty);
this.modifyAwareHandler = new ModifyAwareHandler();
}
ObjectMapper objectMapper() {
@@ -39,17 +30,6 @@ class TypeJsonManager {
return null;
}
/**
* Return the DirtyHandler to use.
*/
DirtyHandler dirtyHandler(Class<?> cls, Class<?> rawType) {
if (!Object.class.equals(cls) || ModifyAwareType.class.isAssignableFrom(rawType)) {
// Set, List and Map are modify aware
return modifyAwareHandler;
}
return defaultHandler;
}
/**
* Return true if the value should be considered dirty (and included in an update).
*/
@@ -71,28 +51,4 @@ class TypeJsonManager {
}
}
static final class ModifyAwareHandler implements DirtyHandler {
@Override
public boolean isDirty(Object value) {
return checkModifyAware(value);
}
}
/**
* Effectively constant based on {@link DatabaseConfig#isJsonDirtyByDefault()}
*/
static final class DefaultHandler implements DirtyHandler {
private final boolean dirty;
DefaultHandler(boolean dirty) {
this.dirty = dirty;
}
@Override
public boolean isDirty(Object value) {
return dirty;
}
}
}
@@ -104,9 +104,6 @@ public class ServerConfigTest {
assertEquals(PlatformConfig.DbUuid.BINARY, serverConfig.getPlatformConfig().getDbUuid());
assertEquals(JsonConfig.DateTime.MILLIS, serverConfig.getJsonDateTime());
assertEquals(JsonConfig.Date.MILLIS, serverConfig.getJsonDate());
assertFalse(serverConfig.isJsonDirtyByDefault());
serverConfig.setJsonDirtyByDefault(true);
assertTrue(serverConfig.isJsonDirtyByDefault());
assertEquals("r0,users,orgs", serverConfig.getEnabledL2Regions());
@@ -159,7 +156,6 @@ public class ServerConfigTest {
assertFalse(serverConfig.isIdGeneratorAutomatic());
assertEquals(JsonConfig.DateTime.ISO8601, serverConfig.getJsonDateTime());
assertEquals(JsonConfig.Date.ISO8601, serverConfig.getJsonDate());
assertTrue(serverConfig.isJsonDirtyByDefault());
assertTrue(serverConfig.getPlatformConfig().isCaseSensitiveCollation());
assertTrue(serverConfig.isAutoLoadModuleInfo());
@@ -76,10 +76,11 @@ public class TestDbJson_List extends BaseTestCase {
update_when_dirty();
update_when_dirty_flags();
update_when_dirty_SetListMap();
DB.delete(found);
}
//@Test//(dependsOnMethods = "insert")
public void json_parse_format() {
private void json_parse_format() {
String asJson = DB.json().toJson(found);
assertThat(asJson).contains("\"tags\":[\"one\",\"two\"]");
@@ -104,8 +105,7 @@ public class TestDbJson_List extends BaseTestCase {
assertThat(fromJson.getBeanMap()).hasSize(2);
}
//@Test//(dependsOnMethods = "insert")
public void update_when_notDirty() {
private void update_when_notDirty() {
found.setName("mod");
LoggedSqlCollector.start();
@@ -117,7 +117,7 @@ public class TestDbJson_List extends BaseTestCase {
assertSql(sql.get(0)).contains("update ebasic_json_list set name=?, version=? where");
}
public void update_when_dirty() {
private void update_when_dirty() {
//found.setName("modAgain");
found.getTags().add("three");
@@ -131,7 +131,7 @@ public class TestDbJson_List extends BaseTestCase {
assertSql(sql.get(0)).contains("update ebasic_json_list set tags=?, version=? where id=? and version=?");
}
public void update_when_dirty_flags() {
private void update_when_dirty_flags() {
//found.setName("modAgain");
found.getFlags().remove(42L);
@@ -145,7 +145,7 @@ public class TestDbJson_List extends BaseTestCase {
assertSql(sql.get(0)).contains("update ebasic_json_list set flags=?, version=? where id=? and version=?;");
}
public void update_when_dirty_SetListMap() {
private void update_when_dirty_SetListMap() {
//found.setName("modAgain");
found.getBeanSet().clear();
@@ -20,6 +20,7 @@ public class EBasicJsonList {
String name;
// @JsonDeserialize(as=LinkedHashSet.class)
@DbJson(length = 700, name = "beans")
Set<PlainBean> beanSet;