diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClasses.java b/src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClasses.java index 7aa6b6ff0..cdd223ce1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClasses.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClasses.java @@ -22,6 +22,7 @@ import org.avaje.classpath.scanner.ClassFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.persistence.AttributeConverter; import javax.persistence.Embeddable; import javax.persistence.Entity; import javax.persistence.Table; @@ -47,12 +48,14 @@ public class BootupClasses implements ClassFilter { private final List>> scalarConverterList = new ArrayList<>(); + private final List>> attributeConverterList = new ArrayList<>(); + private final List>> compoundTypeList = new ArrayList<>(); - // The following objects are instantiiated on first request + // The following objects are instantiated on first request // there is always a candidate list, that holds the class and an - // instance list, that holds the instance. Once a class is instantiiated + // instance list, that holds the instance. Once a class is instantiated // (or added) it will get removed from the candidate list private final List> idGeneratorCandidates = new ArrayList<>(); @@ -337,6 +340,13 @@ public class BootupClasses implements ClassFilter { return scalarConverterList; } + /** + * Return the list of AttributeConverters found. + */ + public List>> getAttributeConverters() { + return attributeConverterList; + } + /** * Return the list of ScalarConverters found. */ @@ -387,6 +397,11 @@ public class BootupClasses implements ClassFilter { interesting = true; } + if (AttributeConverter.class.isAssignableFrom(cls)) { + attributeConverterList.add((Class>) cls); + interesting = true; + } + if (CompoundType.class.isAssignableFrom(cls)) { compoundTypeList.add((Class>) cls); interesting = true; diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/AttributeConverterAdapter.java b/src/main/java/com/avaje/ebeaninternal/server/type/AttributeConverterAdapter.java new file mode 100644 index 000000000..d7bb7e397 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/AttributeConverterAdapter.java @@ -0,0 +1,32 @@ +package com.avaje.ebeaninternal.server.type; + +import com.avaje.ebean.config.ScalarTypeConverter; + +import javax.persistence.AttributeConverter; + +/** + * Adapter from ScalarTypeConverter to AttributeConverter. + */ +class AttributeConverterAdapter implements ScalarTypeConverter { + + private final AttributeConverter converter; + + AttributeConverterAdapter(AttributeConverter converter) { + this.converter = converter; + } + + @Override + public B getNullValue() { + return null; + } + + @Override + public B wrapValue(S dbValue) { + return converter.convertToEntityAttribute(dbValue); + } + + @Override + public S unwrapValue(B beanType) { + return converter.convertToDatabaseColumn(beanType); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java index 867a6c0c9..c597d7978 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java @@ -34,6 +34,7 @@ import org.joda.time.LocalTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.persistence.AttributeConverter; import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -223,6 +224,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { if (bootupClasses != null) { initialiseCustomScalarTypes(jsonDateTime, bootupClasses); initialiseScalarConverters(bootupClasses); + initialiseAttributeConverters(bootupClasses); initialiseCompoundTypes(bootupClasses); } } @@ -813,17 +815,46 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { ScalarTypeConverter converter = (ScalarTypeConverter) cls.newInstance(); ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, converter); - logger.debug("Register ScalarTypeWrapper from " + logicalType + " -> " + persistType + " using:" + cls); - add(stw); } catch (Exception e) { - String msg = "Error loading ScalarType [" + cls.getName() + "]"; - logger.error(msg, e); + logger.error("Error registering ScalarTypeConverter [" + cls.getName() + "]", e); } } + } + @SuppressWarnings({"unchecked", "rawtypes"}) + private void initialiseAttributeConverters(BootupClasses bootupClasses) { + + List>> foundTypes = bootupClasses.getAttributeConverters(); + + for (Class> foundType : foundTypes) { + Class cls = foundType; + try { + + Class[] paramTypes = TypeReflectHelper.getParams(cls, AttributeConverter.class); + if (paramTypes.length != 2) { + throw new IllegalStateException("Expected 2 generics paramtypes but got: " + Arrays.toString(paramTypes)); + } + + Class logicalType = paramTypes[0]; + Class persistType = paramTypes[1]; + + ScalarType wrappedType = getScalarType(persistType); + if (wrappedType == null) { + throw new IllegalStateException("Could not find ScalarType for: " + paramTypes[1]); + } + + AttributeConverter converter = (AttributeConverter) cls.newInstance(); + ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, new AttributeConverterAdapter(converter)); + logger.debug("Register ScalarTypeWrapper from " + logicalType + " -> " + persistType + " using:" + cls); + add(stw); + + } catch (Exception e) { + logger.error("Error registering AttributeConverter [" + cls.getName() + "]", e); + } + } } private void initialiseCompoundTypes(BootupClasses bootupClasses) { diff --git a/src/test/java/com/avaje/tests/model/ivo/ESomeConvertType.java b/src/test/java/com/avaje/tests/model/ivo/ESomeConvertType.java new file mode 100644 index 000000000..43c53b784 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/ivo/ESomeConvertType.java @@ -0,0 +1,44 @@ +package com.avaje.tests.model.ivo; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class ESomeConvertType { + + @Id + Long id; + + String name; + + Rate rate; + + public ESomeConvertType(String name, Rate rate) { + this.name = name; + this.rate = rate; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Rate getRate() { + return rate; + } + + public void setRate(Rate rate) { + this.rate = rate; + } +} diff --git a/src/test/java/com/avaje/tests/model/ivo/converter/RateTypeConverter.java b/src/test/java/com/avaje/tests/model/ivo/converter/RateTypeConverter.java index a545db1ec..e2943c299 100644 --- a/src/test/java/com/avaje/tests/model/ivo/converter/RateTypeConverter.java +++ b/src/test/java/com/avaje/tests/model/ivo/converter/RateTypeConverter.java @@ -2,19 +2,23 @@ package com.avaje.tests.model.ivo.converter; import com.avaje.tests.model.ivo.Rate; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; import java.math.BigDecimal; /** * Converts between Rate and BigDecimal. */ -public class RateTypeConverter {//implements ScalarTypeConverter{ +@Converter +public class RateTypeConverter implements AttributeConverter { - public BigDecimal unwrapValue(Rate beanType) { - return beanType.getValue(); + @Override + public BigDecimal convertToDatabaseColumn(Rate attribute) { + return attribute.getValue(); } - public Rate wrapValue(BigDecimal scalarType) { - return new Rate(scalarType); + @Override + public Rate convertToEntityAttribute(BigDecimal dbData) { + return new Rate(dbData); } - } diff --git a/src/test/java/com/avaje/tests/model/ivo/test/TestAttributeConverter.java b/src/test/java/com/avaje/tests/model/ivo/test/TestAttributeConverter.java new file mode 100644 index 000000000..5ffde37e6 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/ivo/test/TestAttributeConverter.java @@ -0,0 +1,24 @@ +package com.avaje.tests.model.ivo.test; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.tests.model.ivo.ESomeConvertType; +import com.avaje.tests.model.ivo.Rate; +import org.junit.Test; + +public class TestAttributeConverter extends BaseTestCase { + + @Test + public void insertUpdateDelete() { + + ESomeConvertType bean = new ESomeConvertType("one", new Rate(20.1)); + + Ebean.save(bean); + + ESomeConvertType found = Ebean.find(ESomeConvertType.class, bean.getId()); + found.setRate(new Rate("40")); + Ebean.save(found); + + Ebean.delete(found); + } +}