Skip to content

Commit

Permalink
Merge pull request #2248 from ebean-orm/feature/DbJson-DirtyDetection…
Browse files Browse the repository at this point in the history
…-Refactor

Refactor DbJson Jackson handling adding DatabaseConfig.setJsonDirtyByDefault()
  • Loading branch information
rbygrave authored Jun 10, 2021
2 parents 8ba66af + 4e088a4 commit 6fd2f55
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 75 deletions.
29 changes: 28 additions & 1 deletion ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ 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.
*/
Expand Down Expand Up @@ -737,6 +743,26 @@ public void setJsonInclude(JsonConfig.Include jsonInclude) {
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.
*/
Expand Down Expand Up @@ -2909,6 +2935,7 @@ protected void loadSettings(PropertiesWrapper p) {
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);
Expand Down Expand Up @@ -3369,7 +3396,7 @@ public void setLoadModuleInfo(boolean loadModuleInfo) {
this.loadModuleInfo = loadModuleInfo;
}

public enum UuidVersion {
public enum UuidVersion {
VERSION4,
VERSION1,
VERSION1RND
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public final class DefaultTypeManager implements TypeManager {
private final Object objectMapper;
private final boolean objectMapperPresent;
private final boolean postgres;
private final TypeJsonManager jsonManager;
private final boolean offlineMigrationGeneration;
private final EnumType defaultEnumType;

Expand Down Expand Up @@ -131,11 +132,11 @@ public DefaultTypeManager(DatabaseConfig config, BootupClasses bootupClasses) {
this.typeMap = new ConcurrentHashMap<>();
this.nativeMap = new ConcurrentHashMap<>();
this.logicalMap = new ConcurrentHashMap<>();
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.extraTypeFactory = new DefaultTypeFactory(config);
this.postgres = isPostgres(config.getDatabasePlatform());
this.arrayTypeListFactory = arrayTypeListFactory(config.getDatabasePlatform());
this.arrayTypeSetFactory = arrayTypeSetFactory(config.getDatabasePlatform());
this.offlineMigrationGeneration = DbOffline.isGenerateMigration();
Expand Down Expand Up @@ -425,7 +426,7 @@ private ScalarType<?> createJsonObjectMapperType(DeployBeanProperty prop, int db
if (objectMapper == null) {
throw new IllegalArgumentException("Type [" + type + "] unsupported for @DbJson mapping - Jackson ObjectMapper not present");
}
return ScalarTypeJsonObjectMapper.createTypeFor(postgres, (AnnotatedField) prop.getJacksonField(), (ObjectMapper) objectMapper, dbType, docType);
return ScalarTypeJsonObjectMapper.createTypeFor(jsonManager, (AnnotatedField) prop.getJacksonField(), dbType, docType);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public boolean isMutable() {
*/
@Override
public boolean isDirty(Object value) {
return CheckMarkedDirty.isDirty(value);
return TypeJsonManager.checkIsDirty(value);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public boolean isMutable() {
*/
@Override
public boolean isDirty(Object value) {
return CheckMarkedDirty.isDirty(value);
return TypeJsonManager.checkIsDirty(value);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
Expand All @@ -30,47 +29,32 @@
/**
* Supports @DbJson properties using Jackson ObjectMapper.
*/
public class ScalarTypeJsonObjectMapper {
class ScalarTypeJsonObjectMapper {

/**
* Create and return the appropriate ScalarType.
*/
public static ScalarType<?> createTypeFor(boolean postgres, AnnotatedField field, ObjectMapper objectMapper,
int dbType, DocPropertyType docType) {

static ScalarType<?> createTypeFor(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
Class<?> type = field.getRawType();
String pgType = getPostgresType(postgres, dbType);
if (Set.class.equals(type)) {
return new OmSet(objectMapper, field, dbType, pgType, docType);
return new OmSet(jsonManager, field, dbType, docType);
}
if (List.class.equals(type)) {
return new OmList(objectMapper, field, dbType, pgType, docType);
return new OmList(jsonManager, field, dbType, docType);
}
if (Map.class.equals(type)) {
return new OmMap(objectMapper, field, dbType, pgType);
return new OmMap(jsonManager, field, dbType);
}
return new GenericObject(objectMapper, field, dbType, pgType);
}

private static String getPostgresType(boolean postgres, int dbType) {
if (postgres) {
switch (dbType) {
case DbPlatformType.JSON:
return PostgresHelper.JSON_TYPE;
case DbPlatformType.JSONB:
return PostgresHelper.JSONB_TYPE;
}
}
return null;
return new GenericObject(jsonManager, field, dbType, type);
}

/**
* Maps any type (Object) using Jackson ObjectMapper.
*/
private static class GenericObject extends Base<Object> {

public GenericObject(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType) {
super(Object.class, objectMapper, field, dbType, pgType, DocPropertyType.OBJECT);
GenericObject(TypeJsonManager jsonManager, AnnotatedField field, int dbType, Class<?> rawType) {
super(Object.class, jsonManager, field, dbType, DocPropertyType.OBJECT, rawType);
}
}

Expand All @@ -80,8 +64,8 @@ public GenericObject(ObjectMapper objectMapper, AnnotatedField field, int dbType
@SuppressWarnings("rawtypes")
private static class OmSet extends Base<Set> {

public OmSet(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType, DocPropertyType docType) {
super(Set.class, objectMapper, field, dbType, pgType, docType);
OmSet(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
super(Set.class, jsonManager, field, dbType, docType);
}

@Override
Expand All @@ -98,8 +82,8 @@ public Set read(DataReader reader) throws SQLException {
@SuppressWarnings("rawtypes")
private static class OmList extends Base<List> {

public OmList(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType, DocPropertyType docType) {
super(List.class, objectMapper, field, dbType, pgType, docType);
OmList(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
super(List.class, jsonManager, field, dbType, docType);
}

@Override
Expand All @@ -116,8 +100,8 @@ public List read(DataReader reader) throws SQLException {
@SuppressWarnings("rawtypes")
private static class OmMap extends Base<Map> {

public OmMap(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType) {
super(Map.class, objectMapper, field, dbType, pgType, DocPropertyType.OBJECT);
OmMap(TypeJsonManager jsonManager, AnnotatedField field, int dbType) {
super(Map.class, jsonManager, field, dbType, DocPropertyType.OBJECT);
}

@Override
Expand All @@ -135,24 +119,23 @@ public Map read(DataReader reader) throws SQLException {
private static abstract class Base<T> extends ScalarTypeBase<T> {

private final ObjectWriter objectWriter;

private final ObjectMapper objectReader;

private final JavaType deserType;

private final String pgType;

private final DocPropertyType docType;
private final TypeJsonManager.DirtyHandler dirtyHandler;

/**
* Construct given the object mapper, property type and DB type for storage.
*/
public Base(Class<T> cls, ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType, DocPropertyType docType) {
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.pgType = pgType;
this.objectReader = jsonManager.objectMapper();
this.pgType = jsonManager.postgresType(dbType);
this.docType = docType;
this.objectReader = objectMapper;
final JacksonTypeHelper helper = new JacksonTypeHelper(field, objectMapper);
this.dirtyHandler = jsonManager.dirtyHandler(cls, rawType);
final JacksonTypeHelper helper = new JacksonTypeHelper(field, objectReader);
this.deserType = helper.type();
this.objectWriter = helper.objectWriter();
}
Expand All @@ -170,7 +153,7 @@ public boolean isMutable() {
*/
@Override
public boolean isDirty(Object value) {
return CheckMarkedDirty.isDirty(value);
return dirtyHandler.isDirty(value);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public boolean isMutable() {

@Override
public boolean isDirty(Object value) {
return CheckMarkedDirty.isDirty(value);
return TypeJsonManager.checkIsDirty(value);
}

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
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) {
this.postgres = postgres;
this.objectMapper = (ObjectMapper) objectMapper;
this.defaultHandler = new DefaultHandler(defaultDirty);
this.modifyAwareHandler = new ModifyAwareHandler();
}

ObjectMapper objectMapper() {
return objectMapper;
}

String postgresType(int dbType) {
if (postgres) {
switch (dbType) {
case DbPlatformType.JSON:
return PostgresHelper.JSON_TYPE;
case DbPlatformType.JSONB:
return PostgresHelper.JSONB_TYPE;
}
}
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).
*/
static boolean checkIsDirty(Object value) {
if (value instanceof ModifyAwareType) {
return checkModifyAware(value);
}
return true;
}

private static boolean checkModifyAware(Object value) {
ModifyAwareType modifyAware = (ModifyAwareType) value;
if (modifyAware.isMarkedDirty()) {
// reset the dirty state (consider not dirty after update)
modifyAware.setMarkedDirty(false);
return true;
} else {
return false;
}
}

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;
}
}

}
Loading

0 comments on commit 6fd2f55

Please sign in to comment.