#827 - ENH: Add support for AttributeConverter from JPA 2.1

This commit is contained in:
Rob Bygrave
2016-11-24 20:21:48 +13:00
parent 2ab98a922d
commit 7f31104439
6 changed files with 162 additions and 12 deletions
@@ -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<Class<? extends ScalarTypeConverter<?, ?>>> scalarConverterList = new ArrayList<>();
private final List<Class<? extends AttributeConverter<?, ?>>> attributeConverterList = new ArrayList<>();
private final List<Class<? extends CompoundType<?>>> 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<Class<? extends IdGenerator>> idGeneratorCandidates = new ArrayList<>();
@@ -337,6 +340,13 @@ public class BootupClasses implements ClassFilter {
return scalarConverterList;
}
/**
* Return the list of AttributeConverters found.
*/
public List<Class<? extends AttributeConverter<?, ?>>> 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<? extends AttributeConverter<?, ?>>) cls);
interesting = true;
}
if (CompoundType.class.isAssignableFrom(cls)) {
compoundTypeList.add((Class<? extends CompoundType<?>>) cls);
interesting = true;
@@ -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<B,S> implements ScalarTypeConverter<B, S> {
private final AttributeConverter<B,S> converter;
AttributeConverterAdapter(AttributeConverter<B, S> 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);
}
}
@@ -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<Class<? extends AttributeConverter<?, ?>>> foundTypes = bootupClasses.getAttributeConverters();
for (Class<? extends AttributeConverter<?, ?>> 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) {
@@ -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;
}
}
@@ -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<Rate,BigDecimal>{
@Converter
public class RateTypeConverter implements AttributeConverter<Rate,BigDecimal> {
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);
}
}
@@ -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);
}
}