From 22ab89b9ede35efa95fffce76cf62f8a372feea1 Mon Sep 17 00:00:00 2001 From: jasp Date: Mon, 6 Apr 2015 11:53:17 +0300 Subject: [PATCH] Fixing enum array mapping issues --- grails-app/domain/test/array/TestEnum.groovy | 4 +- .../domain/test/criteria/array/Like.groovy | 4 +- .../hibernate/usertype/ArrayType.java | 38 +++++++++++-------- .../hibernate/usertype/BidiEnumMap.java | 15 ++++++-- ...stgresqlArraysDomainIntegrationSpec.groovy | 12 +++--- 5 files changed, 43 insertions(+), 30 deletions(-) diff --git a/grails-app/domain/test/array/TestEnum.groovy b/grails-app/domain/test/array/TestEnum.groovy index 9332873..f37fb50 100644 --- a/grails-app/domain/test/array/TestEnum.groovy +++ b/grails-app/domain/test/array/TestEnum.groovy @@ -13,8 +13,8 @@ class TestEnum { SATURDAY(5), SUNDAY(6) - private final int value - Day(int value) { this.value = value } + final int id + Day(int id) { this.id = id } } Day[] days diff --git a/grails-app/domain/test/criteria/array/Like.groovy b/grails-app/domain/test/criteria/array/Like.groovy index 7b31bc4..c0899ea 100644 --- a/grails-app/domain/test/criteria/array/Like.groovy +++ b/grails-app/domain/test/criteria/array/Like.groovy @@ -24,8 +24,8 @@ class Like { CARROT(7), LEMON(8) - private final int value - Juice(int value) { this.value = value } + private final int id + Juice(int id) { this.id = id } } static mapping = { diff --git a/src/java/net/kaleidos/hibernate/usertype/ArrayType.java b/src/java/net/kaleidos/hibernate/usertype/ArrayType.java index 36afd36..1da5c55 100644 --- a/src/java/net/kaleidos/hibernate/usertype/ArrayType.java +++ b/src/java/net/kaleidos/hibernate/usertype/ArrayType.java @@ -7,10 +7,7 @@ import org.hibernate.usertype.UserType; import java.io.Serializable; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; +import java.sql.*; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -101,18 +98,19 @@ public int[] sqlTypes() { @Override public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException { - Object[] result = null; + Object result = null; Class typeArrayClass = java.lang.reflect.Array.newInstance(typeClass, 0).getClass(); - java.sql.Array array = rs.getArray(names[0]); + Array sqlArray = rs.getArray(names[0]); if (!rs.wasNull()) { + Object array = sqlArray.getArray(); if (typeClass.isEnum()) { - int length = java.lang.reflect.Array.getLength(array); - Object converted = java.lang.reflect.Array.newInstance(typeClass, length); + int length = array != null ? java.lang.reflect.Array.getLength(array) : 0; + result = java.lang.reflect.Array.newInstance(typeClass, length); for (int i = 0; i < length; i++) { - java.lang.reflect.Array.set(converted, i, idToEnum(java.lang.reflect.Array.get(array, i))); + java.lang.reflect.Array.set(result, i, idToEnum((Integer)java.lang.reflect.Array.get(array, i))); } } else { - result = (Object[]) typeArrayClass.cast(array.getArray()); + result = typeArrayClass.cast(array); } } return result; @@ -136,7 +134,7 @@ public void nullSafeSet(PreparedStatement st, Object value, int index, SessionIm if (valueToSet[i] instanceof Integer) { converted[i] = (Integer) valueToSet[i]; } else { - converted[i] = ((Enum) valueToSet[i]).ordinal(); + converted[i] = enumToId(valueToSet[i]); } } valueToSet = converted; @@ -150,14 +148,22 @@ public Class getTypeClass() { return typeClass; } - private Object idToEnum(Object id) throws HibernateException { + private void ensureBidiMapInitialized() throws HibernateException { try { - if (bidiMap == null) { + if (bidiMap == null) bidiMap = new BidiEnumMap(typeClass); - } - return bidiMap.getEnumValue(id); } catch (Exception e) { throw new HibernateException("Unable to create bidirectional enum map for " + typeClass, e); } } -} + + private Object idToEnum(int id) throws HibernateException { + ensureBidiMapInitialized(); + return bidiMap.getEnumValue(id); + } + + private int enumToId(Object enumValue) throws HibernateException { + ensureBidiMapInitialized(); + return bidiMap.getKey(enumValue); + } +} \ No newline at end of file diff --git a/src/java/net/kaleidos/hibernate/usertype/BidiEnumMap.java b/src/java/net/kaleidos/hibernate/usertype/BidiEnumMap.java index 14fba7d..4889dd7 100644 --- a/src/java/net/kaleidos/hibernate/usertype/BidiEnumMap.java +++ b/src/java/net/kaleidos/hibernate/usertype/BidiEnumMap.java @@ -29,7 +29,7 @@ public BidiEnumMap(Class enumClass) throws NoSuchMethodException, IllegalAcce EnumMap enumToKey = new EnumMap(enumClass); HashMap keytoEnum = new HashMap(); - Method idAccessor = enumClass.getMethod(ENUM_ID_ACCESSOR); + Method idAccessor = getIdAccessor(enumClass); Method valuesAccessor = enumClass.getMethod("values"); Object[] values = (Object[]) valuesAccessor.invoke(enumClass); @@ -47,11 +47,18 @@ public BidiEnumMap(Class enumClass) throws NoSuchMethodException, IllegalAcce this.keytoEnum = Collections.unmodifiableMap(keytoEnum); } - public Object getEnumValue(Object id) { + private Method getIdAccessor(Class enumClass) throws NoSuchMethodException { + for (Method method : enumClass.getMethods()) + if (method.getName().equals(ENUM_ID_ACCESSOR)) + return method; + return enumClass.getMethod("ordinal"); + } + + public Object getEnumValue(int id) { return keytoEnum.get(id); } - public Object getKey(Object enumValue) { - return enumToKey.get(enumValue); + public int getKey(Object enumValue) { + return (Integer)enumToKey.get(enumValue); } } diff --git a/test/integration/net/kaleidos/hibernate/array/PostgresqlArraysDomainIntegrationSpec.groovy b/test/integration/net/kaleidos/hibernate/array/PostgresqlArraysDomainIntegrationSpec.groovy index 9b40297..92326e6 100644 --- a/test/integration/net/kaleidos/hibernate/array/PostgresqlArraysDomainIntegrationSpec.groovy +++ b/test/integration/net/kaleidos/hibernate/array/PostgresqlArraysDomainIntegrationSpec.groovy @@ -88,15 +88,15 @@ class PostgresqlArraysDomainIntegrationSpec extends Specification { @Unroll void 'save a domain class with an enum array value #days'() { - setup: - def testEnum = new TestEnum(days: days) - when: - testEnum.save(flush: true) + // Domain saving and retrieving should be in different sessions. Only in that case Hibernate will invoke + // nullSafeGet on the corresponding user type and will not use current session's cache. + def id = TestEnum.withNewSession { + new TestEnum(days: days).save(flush: true, failOnError: true).id + } then: - testEnum.hasErrors() == false - testEnum.days?.length == days?.size() + TestEnum.get(id).days as List == days where: days << [null, [], [TestEnum.Day.MONDAY], [TestEnum.Day.SUNDAY, TestEnum.Day.SATURDAY], [TestEnum.Day.WEDNESDAY, TestEnum.Day.THURSDAY, TestEnum.Day.TUESDAY]]