diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java index 74a7bd5b5..4d12ed957 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java @@ -48,6 +48,7 @@ public final class DeployUtil { private final EncryptKeyManager encryptKeyManager; private final Encryptor bytesEncryptor; private final boolean useValidationNotNull; + private final MutationDetection defaultJsonMutationDetection; public DeployUtil(TypeManager typeMgr, DatabaseConfig config) { this.typeManager = typeMgr; @@ -58,6 +59,7 @@ public final class DeployUtil { Encryptor be = config.getEncryptor(); this.bytesEncryptor = be != null ? be : new SimpleAesEncryptor(); this.useValidationNotNull = config.isUseValidationNotNull(); + this.defaultJsonMutationDetection = config.getJsonMutationDetection(); } public TypeManager getTypeManager() { @@ -203,7 +205,7 @@ public final class DeployUtil { private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength, MutationDetection mutationDetection) { prop.setDbType(dbType); - prop.setMutationDetection(mutationDetection); + prop.setMutationDetection(mutationDetection == MutationDetection.DEFAULT ? defaultJsonMutationDetection : mutationDetection); ScalarType scalarType = typeManager.getJsonScalarType(prop, dbType, dbLength); if (scalarType == null) { throw new RuntimeException("No ScalarType for JSON property [" + prop + "] [" + dbType + "]"); 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 969e0ef64..5347a6433 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 @@ -2,7 +2,12 @@ package io.ebeaninternal.server.type; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import io.ebean.annotation.*; +import io.ebean.annotation.DbArray; +import io.ebean.annotation.DbEnumType; +import io.ebean.annotation.DbEnumValue; +import io.ebean.annotation.EnumValue; +import io.ebean.annotation.MutationDetection; +import io.ebean.annotation.Platform; import io.ebean.config.DatabaseConfig; import io.ebean.config.JsonConfig; import io.ebean.config.PlatformConfig; @@ -36,13 +41,41 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; -import java.net.*; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.URI; +import java.net.URL; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; -import java.time.*; -import java.util.*; +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.Instant; +import java.time.Month; +import java.time.MonthDay; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Currency; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.TimeZone; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** @@ -123,6 +156,8 @@ public final class DefaultTypeManager implements TypeManager { private final PlatformArrayTypeFactory arrayTypeSetFactory; private GeoTypeBinder geoTypeBinder; + private final MutationDetection defaultJsonMutationDetection; + /** * Create the DefaultTypeManager. */ @@ -142,6 +177,7 @@ public final class DefaultTypeManager implements TypeManager { this.offlineMigrationGeneration = DbOffline.isGenerateMigration(); this.defaultEnumType = config.getDefaultEnumType(); this.fileType = new ScalarTypeFile(config.getTempFileProvider()); + this.defaultJsonMutationDetection = config.getJsonMutationDetection(); initialiseStandard(config); initialiseJavaTimeTypes(config); @@ -326,7 +362,8 @@ public final class DefaultTypeManager implements TypeManager { Type genericType = prop.getGenericType(); boolean hasJacksonAnnotations = objectMapperPresent && checkJacksonAnnotations(prop); - boolean keepSource = prop.getMutationDetection() == MutationDetection.SOURCE; + boolean keepSource = prop.getMutationDetection() == MutationDetection.SOURCE + || (prop.getMutationDetection() == MutationDetection.DEFAULT && defaultJsonMutationDetection == MutationDetection.SOURCE); if (type.equals(List.class)) { DocPropertyType docType = getDocType(genericType); if (!hasJacksonAnnotations && isValueTypeSimple(genericType)) { diff --git a/ebean-test/src/test/java/org/tests/json/TestDbJson_List.java b/ebean-test/src/test/java/org/tests/json/TestDbJson_List.java index ce84f08b3..b757dac6f 100644 --- a/ebean-test/src/test/java/org/tests/json/TestDbJson_List.java +++ b/ebean-test/src/test/java/org/tests/json/TestDbJson_List.java @@ -2,8 +2,13 @@ package org.tests.json; import io.ebean.BaseTestCase; import io.ebean.DB; +import io.ebean.Database; +import io.ebean.DatabaseFactory; +import io.ebean.ValuePair; import io.ebean.annotation.ForPlatform; +import io.ebean.annotation.MutationDetection; import io.ebean.annotation.Platform; +import io.ebean.config.DatabaseConfig; import io.ebean.test.LoggedSql; import io.ebean.text.TextException; import org.junit.jupiter.api.Test; @@ -11,10 +16,18 @@ import org.tests.model.json.EBasicJsonList; import org.tests.model.json.PlainBean; import javax.persistence.PersistenceException; -import java.util.*; +import java.util.ArrayList; +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.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; public class TestDbJson_List extends BaseTestCase { @@ -231,4 +244,42 @@ public class TestDbJson_List extends BaseTestCase { assertThat(bean.getTags()).isEmpty(); assertThat(bean.getBeanMap()).isEmpty(); } + + @Test + @ForPlatform(Platform.H2) + public void testDirtyValues() { + DatabaseConfig config = new DatabaseConfig(); + config.loadFromProperties(); + config.setDefaultServer(true); + config.setRegister(true); + config.setDdlRun(false); + config.setJsonMutationDetection(MutationDetection.SOURCE); + Database db = DatabaseFactory.create(config); + try { + assertThat(db).isNotNull(); + + EBasicJsonList bean = new EBasicJsonList(); + bean.getTags().add("aa"); + bean.getTags().add("bb"); + + db.save(bean); + bean = db.find(EBasicJsonList.class, bean.getId()); + + bean.getTags().add("cc"); + final Map dirtyValues = db.beanState(bean).dirtyValues(); + assertThat(dirtyValues).containsOnlyKeys("tags"); + + final ValuePair diff = dirtyValues.get("tags"); + assertThat(diff.getOldValue()).isInstanceOf(List.class).asList() + .containsExactly("aa", "bb"); + assertThat(diff.getNewValue()).isInstanceOf(List.class).asList() + .containsExactly("aa", "bb", "cc"); + } finally { + if (db != null) { + db.shutdown(); + } + } + + + } }