diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java index ffcb90591..8f524f1ac 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java @@ -18,7 +18,6 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; import io.ebeaninternal.server.type.DataEncryptSupport; import io.ebeaninternal.server.type.ScalarType; import io.ebeaninternal.server.type.ScalarTypeArray; -import io.ebeaninternal.server.type.ScalarTypeEnumStandard; import io.ebeaninternal.server.type.SimpleAesEncryptor; import io.ebeaninternal.server.type.TypeManager; import org.slf4j.Logger; @@ -121,14 +120,8 @@ public class DeployUtil { if (scalarType == null) { // look for @DbEnumValue or @EnumValue annotations etc Class> enumClass = (Class>) enumType; - scalarType = typeManager.createEnumScalarType(enumClass); - if (scalarType == null) { - // use JPA normal Enum type (without mapping) - EnumType type = enumerated != null ? enumerated.value() : null; - scalarType = createEnumScalarTypePerSpec(enumType, type); - } - - typeManager.addEnumType(scalarType, enumClass); + EnumType type = enumerated != null ? enumerated.value() : null; + scalarType = typeManager.createEnumScalarType(enumClass, type); } prop.setScalarType(scalarType); prop.setDbType(scalarType.getJdbcType()); @@ -145,20 +138,6 @@ public class DeployUtil { && scalarType.getJdbcType() != Types.VARCHAR; } - private ScalarType createEnumScalarTypePerSpec(Class enumType, EnumType type) { - - if (type == null) { - // default as per spec is ORDINAL - return new ScalarTypeEnumStandard.OrdinalEnum(enumType); - - } else if (type == EnumType.ORDINAL) { - return new ScalarTypeEnumStandard.OrdinalEnum(enumType); - - } else { - return new ScalarTypeEnumStandard.StringEnum(enumType); - } - } - /** * Find the ScalarType for this property. *

diff --git a/src/main/java/io/ebeaninternal/server/type/ArrayElementConverter.java b/src/main/java/io/ebeaninternal/server/type/ArrayElementConverter.java index 9cad99fc8..e13a7d212 100644 --- a/src/main/java/io/ebeaninternal/server/type/ArrayElementConverter.java +++ b/src/main/java/io/ebeaninternal/server/type/ArrayElementConverter.java @@ -12,6 +12,10 @@ interface ArrayElementConverter { */ T toElement(Object rawValue); + default Object[] toDbArray(Object[] objects) { + return objects; + } + /** * The UUID converter implementation. */ @@ -94,4 +98,30 @@ interface ArrayElementConverter { } } + /** + * String converter (noop based). + */ + class EnumConverter implements ArrayElementConverter { + + private final ScalarType scalarType; + + EnumConverter(ScalarType scalarType) { + this.scalarType = scalarType; + } + + @Override + public Object toElement(Object rawValue) { + return scalarType.parse(rawValue.toString()); + } + + @Override + public Object[] toDbArray(Object[] objects) { + Object[] dbArray = new Object[objects.length]; + for (int i = 0; i < objects.length; i++) { + dbArray[i] = scalarType.format(objects[i]); + } + return dbArray; + } + } + } diff --git a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index bbb81b501..439b4f347 100644 --- a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -1,22 +1,22 @@ package io.ebeaninternal.server.type; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import io.ebean.annotation.DbArray; import io.ebean.annotation.DbEnumType; import io.ebean.annotation.DbEnumValue; import io.ebean.annotation.EnumValue; -import io.ebean.config.JsonConfig; import io.ebean.annotation.Platform; +import io.ebean.config.JsonConfig; import io.ebean.config.ScalarTypeConverter; import io.ebean.config.ServerConfig; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.util.AnnotationUtil; -import io.ebeaninternal.dbmigration.DbOffline; import io.ebeaninternal.api.ExtraTypeFactory; +import io.ebeaninternal.dbmigration.DbOffline; import io.ebeaninternal.server.core.bootup.BootupClasses; import io.ebeanservice.docstore.api.mapping.DocPropertyType; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; @@ -25,6 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.persistence.AttributeConverter; +import javax.persistence.EnumType; import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -337,12 +338,18 @@ public final class DefaultTypeManager implements TypeManager { Type valueType = getValueType(genericType); if (type.equals(List.class)) { if (arrayTypeListFactory != null) { + if(isEnumType(valueType)) { + return arrayTypeListFactory.typeForEnum(createEnumScalarType(asEnumClass(valueType), EnumType.STRING)); + } return arrayTypeListFactory.typeFor(valueType); } // fallback to JSON storage in VARCHAR column return new ScalarTypeJsonList.Varchar(getDocType(valueType)); } else if (type.equals(Set.class)) { if (arrayTypeSetFactory != null) { + if(isEnumType(valueType)) { + return arrayTypeSetFactory.typeForEnum(createEnumScalarType(asEnumClass(valueType), EnumType.STRING)); + } return arrayTypeSetFactory.typeFor(valueType); } // fallback to JSON storage in VARCHAR column @@ -351,6 +358,15 @@ public final class DefaultTypeManager implements TypeManager { throw new IllegalStateException("Type [" + type + "] not supported for @DbArray"); } + @SuppressWarnings("unchecked") + private Class> asEnumClass(Type valueType) { + return (Class>)valueType; + } + + private boolean isEnumType(Type valueType) { + return valueType instanceof Class && ((Class)valueType).isEnum(); + } + @Override public ScalarType getJsonScalarType(Class type, int dbType, int dbLength, Type genericType) { @@ -571,7 +587,36 @@ public final class DefaultTypeManager implements TypeManager { *

*/ @Override - public ScalarType createEnumScalarType(Class> enumType) { + public ScalarType createEnumScalarType(Class> enumType, EnumType type) { + + ScalarType scalarType = getScalarType(enumType); + if (scalarType != null) { + return scalarType; + } + + scalarType = createEnumScalarTypePerExtentions(enumType); + if (scalarType == null) { + // use JPA normal Enum type (without mapping) + scalarType = createEnumScalarTypePerSpec(enumType, type); + } + addEnumType(scalarType, enumType); + return scalarType; + } + + private ScalarType createEnumScalarTypePerSpec(Class enumType, EnumType type) { + if (type == null) { + // default as per spec is ORDINAL + return new ScalarTypeEnumStandard.OrdinalEnum(enumType); + + } else if (type == EnumType.ORDINAL) { + return new ScalarTypeEnumStandard.OrdinalEnum(enumType); + + } else { + return new ScalarTypeEnumStandard.StringEnum(enumType); + } + } + + private ScalarType createEnumScalarTypePerExtentions(Class> enumType) { Method[] methods = enumType.getMethods(); for (Method method : methods) { diff --git a/src/main/java/io/ebeaninternal/server/type/PlatformArrayTypeFactory.java b/src/main/java/io/ebeaninternal/server/type/PlatformArrayTypeFactory.java index 90251fae7..11bcf8613 100644 --- a/src/main/java/io/ebeaninternal/server/type/PlatformArrayTypeFactory.java +++ b/src/main/java/io/ebeaninternal/server/type/PlatformArrayTypeFactory.java @@ -12,4 +12,8 @@ public interface PlatformArrayTypeFactory { */ ScalarType typeFor(Type valueType); + /** + * Return the ScalarType to handle DB ARRAY for the given enum element type. + */ + ScalarType typeForEnum(ScalarType scalarType); } diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayList.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayList.java index 9673c39c1..424f8da8b 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayList.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayList.java @@ -1,11 +1,11 @@ package io.ebeaninternal.server.type; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; import io.ebean.text.json.EJson; import io.ebeaninternal.json.ModifyAwareList; import io.ebeanservice.docstore.api.mapping.DocPropertyType; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; import javax.persistence.PersistenceException; import java.io.IOException; @@ -57,6 +57,12 @@ public class ScalarTypeArrayList extends ScalarTypeJsonCollection implemen } throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public ScalarTypeArrayList typeForEnum(ScalarType scalarType) { + return new ScalarTypeArrayList("varchar", DocPropertyType.TEXT, new ArrayElementConverter.EnumConverter(scalarType)); + } } private final String arrayType; @@ -92,7 +98,7 @@ public class ScalarTypeArrayList extends ScalarTypeJsonCollection implemen } protected Object[] toArray(List value) { - return value.toArray(); + return converter.toDbArray(value.toArray()); } @Override diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2.java index f41dd0418..e793a86f2 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2.java @@ -46,6 +46,11 @@ class ScalarTypeArrayListH2 extends ScalarTypeArrayList { } throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); } + + @Override + public ScalarType typeForEnum(ScalarType scalarType) { + return new ScalarTypeArrayListH2("varchar", DocPropertyType.TEXT, new ArrayElementConverter.EnumConverter(scalarType)); + } } @SuppressWarnings("rawtypes") diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySet.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySet.java index d12cea509..29fc9d0f8 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySet.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySet.java @@ -56,6 +56,12 @@ public class ScalarTypeArraySet extends ScalarTypeJsonCollection> impl } throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping on set"); } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public ScalarTypeArraySet typeForEnum(ScalarType scalarType) { + return new ScalarTypeArraySet("varchar", DocPropertyType.TEXT, new ArrayElementConverter.EnumConverter(scalarType)); + } } private final String arrayType; @@ -91,7 +97,7 @@ public class ScalarTypeArraySet extends ScalarTypeJsonCollection> impl } protected Object[] toArray(Set value) { - return value.toArray(); + return converter.toDbArray(value.toArray()); } @Override diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2.java index 03aa65a6c..2c7ac0123 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2.java @@ -28,6 +28,7 @@ class ScalarTypeArraySetH2 extends ScalarTypeArraySet { * Return the ScalarType to use based on the List's generic parameter type. */ @Override + @SuppressWarnings("unchecked") public ScalarTypeArraySetH2 typeFor(Type valueType) { if (valueType.equals(java.util.UUID.class)) { return UUID; @@ -46,9 +47,15 @@ class ScalarTypeArraySetH2 extends ScalarTypeArraySet { } throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); } + + @Override + public ScalarTypeArraySetH2 typeForEnum(ScalarType scalarType) { + return new ScalarTypeArraySetH2("varchar", DocPropertyType.TEXT, new ArrayElementConverter.EnumConverter(scalarType)); + } } - private ScalarTypeArraySetH2(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) { + @SuppressWarnings("unchecked") + private ScalarTypeArraySetH2(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) { super(arrayType, docPropertyType, converter); } diff --git a/src/main/java/io/ebeaninternal/server/type/TypeManager.java b/src/main/java/io/ebeaninternal/server/type/TypeManager.java index 9e5e39aec..15827cfbe 100644 --- a/src/main/java/io/ebeaninternal/server/type/TypeManager.java +++ b/src/main/java/io/ebeaninternal/server/type/TypeManager.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.type; import io.ebean.annotation.DbArray; +import javax.persistence.EnumType; import java.lang.reflect.Type; /** @@ -43,7 +44,7 @@ public interface TypeManager { * Create a ScalarType for an Enum using a mapping (rather than JPA Ordinal * or String which has limitations). */ - ScalarType createEnumScalarType(Class> enumType); + ScalarType createEnumScalarType(Class> enumType, EnumType enumerated); /** * Return the ScalarType used to handle JSON content. diff --git a/src/test/java/io/ebean/server/type/TestTypeManager.java b/src/test/java/io/ebean/server/type/TestTypeManager.java index cace36412..4e552b69f 100644 --- a/src/test/java/io/ebean/server/type/TestTypeManager.java +++ b/src/test/java/io/ebean/server/type/TestTypeManager.java @@ -7,10 +7,10 @@ import io.ebeaninternal.server.core.bootup.BootupClasses; import io.ebeaninternal.server.type.DefaultTypeManager; import io.ebeaninternal.server.type.RsetDataReader; import io.ebeaninternal.server.type.ScalarType; -import org.tests.model.ivo.Money; -import org.tests.model.ivo.converter.MoneyTypeConverter; import org.junit.Assert; import org.junit.Test; +import org.tests.model.ivo.Money; +import org.tests.model.ivo.converter.MoneyTypeConverter; import java.sql.SQLException; import java.sql.Types; @@ -24,7 +24,7 @@ public class TestTypeManager extends BaseTestCase { DefaultTypeManager typeManager = createTypeManager(); - ScalarType type = typeManager.createEnumScalarType(MyEnum.class); + ScalarType type = typeManager.createEnumScalarType(MyEnum.class, null); typeManager.addEnumType(type, MyEnum.class); Object val = type.read(new DummyDataReader("A")); @@ -49,7 +49,7 @@ public class TestTypeManager extends BaseTestCase { DefaultTypeManager typeManager = createTypeManager(); - ScalarType dayOfWeekType = typeManager.createEnumScalarType(MyDayOfWeek.class); + ScalarType dayOfWeekType = typeManager.createEnumScalarType(MyDayOfWeek.class, null); Object val = dayOfWeekType.read(new DummyDataReader("MONDAY ")); assertThat(val).isEqualTo(MyDayOfWeek.MONDAY); diff --git a/src/test/java/org/tests/model/array/EArrayBean.java b/src/test/java/org/tests/model/array/EArrayBean.java index 417b9345a..853fe9976 100644 --- a/src/test/java/org/tests/model/array/EArrayBean.java +++ b/src/test/java/org/tests/model/array/EArrayBean.java @@ -8,11 +8,16 @@ import javax.persistence.Id; import javax.persistence.Version; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.UUID; @Entity public class EArrayBean { + enum Status { + ONE, TWO, THREE + } + @Id Long id; @@ -30,6 +35,12 @@ public class EArrayBean { @DbArray List doubs; + @DbArray + List statuses; + + @DbArray + Set status2; + @Version Long version; @@ -81,6 +92,23 @@ public class EArrayBean { this.doubs = doubs; } + public List getStatuses() { + return statuses; + } + + public void setStatuses(List statuses) { + this.statuses = statuses; + } + + + public Set getStatus2() { + return status2; + } + + public void setStatus2(Set status2) { + this.status2 = status2; + } + public Long getVersion() { return version; } diff --git a/src/test/java/org/tests/model/array/TestDbArray_basic.java b/src/test/java/org/tests/model/array/TestDbArray_basic.java index 92c4f0bdc..e816edc86 100644 --- a/src/test/java/org/tests/model/array/TestDbArray_basic.java +++ b/src/test/java/org/tests/model/array/TestDbArray_basic.java @@ -7,7 +7,9 @@ import org.ebeantest.LoggedSqlCollector; import org.junit.Test; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -39,6 +41,13 @@ public class TestDbArray_basic extends BaseTestCase { bean.getOtherIds().add(96L); bean.getOtherIds().add(97L); bean.setDoubs(doubles); + bean.setStatuses(new ArrayList<>()); + bean.getStatuses().add(EArrayBean.Status.ONE); + bean.getStatuses().add(EArrayBean.Status.THREE); + + bean.setStatus2(new LinkedHashSet<>()); + bean.getStatus2().add(EArrayBean.Status.TWO); + bean.getStatus2().add(EArrayBean.Status.ONE); Ebean.save(bean); @@ -53,10 +62,18 @@ public class TestDbArray_basic extends BaseTestCase { .arrayContains("uids", bean.getUids().get(0)) .arrayContains("phoneNumbers", "9823") .arrayIsNotEmpty("phoneNumbers") + .arrayContains("statuses", EArrayBean.Status.ONE) + .arrayContains("status2", EArrayBean.Status.TWO) .query(); List list = query.findList(); + List statuses = list.get(0).getStatuses(); + Set status2 = list.get(0).getStatus2(); + + assertThat(statuses).contains(EArrayBean.Status.ONE, EArrayBean.Status.THREE); + assertThat(status2).contains(EArrayBean.Status.ONE, EArrayBean.Status.TWO); + assertThat(query.getGeneratedSql()).contains(" t0.other_ids @> array[?,?]::bigint[] "); assertThat(query.getGeneratedSql()).contains(" t0.uids @> array[?] "); assertThat(query.getGeneratedSql()).contains(" t0.phone_numbers @> array[?] ");