Allow LiveObjectService to work with classes that inherit from REntities

pull/1051/head
Simon Jacobs 8 years ago
parent 3241e3bbff
commit d6979d436b

@ -112,8 +112,7 @@ public class RedissonLiveObjectService implements RLiveObjectService {
private <T> Object generateId(Class<T> entityClass) throws NoSuchFieldException {
String idFieldName = getRIdFieldName(entityClass);
RId annotation = entityClass
.getDeclaredField(idFieldName)
RId annotation = ClassUtils.getDeclaredField(entityClass, idFieldName)
.getAnnotation(RId.class);
Resolver resolver = resolverProvider.getResolver(entityClass,
annotation.generator(), annotation);
@ -182,7 +181,7 @@ public class RedissonLiveObjectService implements RLiveObjectService {
throw new IllegalArgumentException("This REntity already exists.");
}
for (FieldDescription.InDefinedShape field : Introspectior.getFieldsDescription(detachedObject.getClass())) {
for (FieldDescription.InDefinedShape field : Introspectior.getAllFields(detachedObject.getClass())) {
Object object = ClassUtils.getField(detachedObject, field.getName());
if (object == null) {
@ -198,7 +197,7 @@ public class RedissonLiveObjectService implements RLiveObjectService {
if (rObject instanceof Collection) {
for (Object obj : (Collection<Object>)object) {
if (obj != null && obj.getClass().isAnnotationPresent(REntity.class)) {
if (obj != null && ClassUtils.isAnnotationPresent(obj.getClass(), REntity.class)) {
Object persisted = alreadyPersisted.get(obj);
if (persisted == null) {
if (checkCascade(detachedObject, type, field.getName())) {
@ -216,7 +215,7 @@ public class RedissonLiveObjectService implements RLiveObjectService {
Object key = entry.getKey();
Object value = entry.getValue();
if (key != null && key.getClass().isAnnotationPresent(REntity.class)) {
if (key != null && ClassUtils.isAnnotationPresent(key.getClass(), REntity.class)) {
Object persisted = alreadyPersisted.get(key);
if (persisted == null) {
if (checkCascade(detachedObject, type, field.getName())) {
@ -226,7 +225,7 @@ public class RedissonLiveObjectService implements RLiveObjectService {
key = persisted;
}
if (value != null && value.getClass().isAnnotationPresent(REntity.class)) {
if (value != null && ClassUtils.isAnnotationPresent(value.getClass(), REntity.class)) {
Object persisted = alreadyPersisted.get(value);
if (persisted == null) {
if (checkCascade(detachedObject, type, field.getName())) {
@ -240,7 +239,7 @@ public class RedissonLiveObjectService implements RLiveObjectService {
}
}
excludedFields.add(field.getName());
} else if (object.getClass().isAnnotationPresent(REntity.class)) {
} else if (ClassUtils.isAnnotationPresent(object.getClass(), REntity.class)) {
Object persisted = alreadyPersisted.get(object);
if (persisted == null) {
if (checkCascade(detachedObject, type, field.getName())) {
@ -576,7 +575,7 @@ public class RedissonLiveObjectService implements RLiveObjectService {
if (entityClass.isAnonymousClass() || entityClass.isLocalClass()) {
throw new IllegalArgumentException(entityClass.getName() + " is not publically accessable.");
}
if (!entityClass.isAnnotationPresent(REntity.class)) {
if (!ClassUtils.isAnnotationPresent(entityClass, REntity.class)) {
throw new IllegalArgumentException("REntity annotation is missing from class type declaration.");
}
FieldList<FieldDescription.InDefinedShape> fieldsWithRIdAnnotation
@ -591,11 +590,11 @@ public class RedissonLiveObjectService implements RLiveObjectService {
String idFieldName = idFieldDescription.getName();
Field idField = null;
try {
idField = entityClass.getDeclaredField(idFieldName);
idField = ClassUtils.getDeclaredField(entityClass, idFieldName);
} catch (Exception e) {
throw new IllegalStateException(e);
}
if (idField.getType().isAnnotationPresent(REntity.class)) {
if (ClassUtils.isAnnotationPresent(idField.getType(), REntity.class)) {
throw new IllegalArgumentException("Field with RId annotation cannot be a type of which class is annotated with REntity.");
}
if (idField.getType().isAssignableFrom(RObject.class)) {

@ -20,6 +20,7 @@ import org.redisson.client.codec.Codec;
import org.redisson.api.RObject;
import org.redisson.api.RObjectReactive;
import org.redisson.api.annotation.REntity;
import org.redisson.liveobject.misc.ClassUtils;
import org.redisson.misc.BiHashMap;
import org.redisson.reactive.RedissonAtomicLongReactive;
import org.redisson.reactive.RedissonBitSetReactive;
@ -77,7 +78,7 @@ public class RedissonReference implements Serializable {
}
public RedissonReference(Class type, String keyName, Codec codec) {
if (!type.isAnnotationPresent(REntity.class) && !RObject.class.isAssignableFrom(type) && !RObjectReactive.class.isAssignableFrom(type)) {
if (!ClassUtils.isAnnotationPresent(type, REntity.class) && !RObject.class.isAssignableFrom(type) && !RObjectReactive.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Class reference has to be a type of either RObject or RLiveObject or RObjectReactive");
}
this.type = RObjectReactive.class.isAssignableFrom(type)
@ -130,7 +131,7 @@ public class RedissonReference implements Serializable {
* @param type the type to set
*/
public void setType(Class<?> type) {
if (!type.isAnnotationPresent(REntity.class) && (!RObject.class.isAssignableFrom(type) || !RObjectReactive.class.isAssignableFrom(type))) {
if (!ClassUtils.isAnnotationPresent(type, REntity.class) && (!RObject.class.isAssignableFrom(type) || !RObjectReactive.class.isAssignableFrom(type))) {
throw new IllegalArgumentException("Class reference has to be a type of either RObject or RLiveObject or RObjectReactive");
}
this.type = type.getName();

@ -21,6 +21,7 @@ import org.redisson.client.codec.Codec;
import org.redisson.api.RObject;
import org.redisson.api.annotation.REntity;
import org.redisson.api.annotation.RObjectField;
import org.redisson.liveobject.misc.ClassUtils;
/**
*
@ -44,7 +45,7 @@ public class DefaultCodecProvider implements CodecProvider {
@Override
public <T extends Codec> T getCodec(REntity anno, Class<?> cls) {
if (!cls.isAnnotationPresent(anno.annotationType())) {
if (!ClassUtils.isAnnotationPresent(cls, anno.annotationType())) {
throw new IllegalArgumentException("Annotation REntity does not present on type [" + cls.getCanonicalName() + "]");
}
return this.<T>getCodec((Class<T>) anno.codec());
@ -53,7 +54,7 @@ public class DefaultCodecProvider implements CodecProvider {
@Override
public <T extends Codec, K extends RObject> T getCodec(RObjectField anno, Class<?> cls, Class<K> rObjectClass, String fieldName) {
try {
if (!cls.getField(fieldName).isAnnotationPresent(anno.getClass())) {
if (!ClassUtils.getDeclaredField(cls, fieldName).isAnnotationPresent(anno.getClass())) {
throw new IllegalArgumentException("Annotation RObjectField does not present on field " + fieldName + " of type [" + cls.getCanonicalName() + "]");
}
} catch (Exception ex) {

@ -30,6 +30,7 @@ import org.redisson.api.annotation.REntity.TransformationMode;
import org.redisson.api.annotation.RId;
import org.redisson.client.codec.Codec;
import org.redisson.codec.CodecProvider;
import org.redisson.liveobject.misc.ClassUtils;
import org.redisson.liveobject.misc.Introspectior;
import org.redisson.liveobject.resolver.NamingScheme;
import org.redisson.misc.RedissonObjectFactory;
@ -73,7 +74,7 @@ public class AccessorInterceptor {
}
String fieldName = getFieldName(method);
Class<?> fieldType = me.getClass().getSuperclass().getDeclaredField(fieldName).getType();
Class<?> fieldType = ClassUtils.getDeclaredField(me.getClass().getSuperclass(), fieldName).getType();
if (isGetter(method, fieldName)) {
Object result = liveMap.get(fieldName);
@ -92,7 +93,7 @@ public class AccessorInterceptor {
}
if (isSetter(method, fieldName)) {
Object arg = args[0];
if (arg != null && arg.getClass().isAnnotationPresent(REntity.class)) {
if (arg != null && ClassUtils.isAnnotationPresent(arg.getClass(), REntity.class)) {
throw new IllegalStateException("REntity object should be attached to Redisson first");
}
@ -100,7 +101,7 @@ public class AccessorInterceptor {
RLiveObject liveObject = (RLiveObject) arg;
Class<? extends Object> rEntity = liveObject.getClass().getSuperclass();
REntity anno = rEntity.getAnnotation(REntity.class);
REntity anno = ClassUtils.getAnnotation(rEntity, REntity.class);
NamingScheme ns = anno.namingScheme()
.getDeclaredConstructor(Codec.class)
.newInstance(codecProvider.getCodec(anno, (Class) rEntity));
@ -113,8 +114,8 @@ public class AccessorInterceptor {
if (!(arg instanceof RObject)
&& (arg instanceof Collection || arg instanceof Map)
&& TransformationMode.ANNOTATION_BASED
.equals(me.getClass().getSuperclass()
.getAnnotation(REntity.class).fieldTransformation())) {
.equals(ClassUtils.getAnnotation(me.getClass().getSuperclass(),
REntity.class).fieldTransformation())) {
RObject rObject = objectBuilder.createObject(((RLiveObject) me).getLiveObjectId(), me.getClass().getSuperclass(), arg.getClass(), fieldName);
if (arg != null) {
if (rObject instanceof Collection) {

@ -30,6 +30,7 @@ import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.api.annotation.REntity;
import org.redisson.codec.CodecProvider;
import org.redisson.liveobject.misc.ClassUtils;
import org.redisson.liveobject.resolver.NamingScheme;
/**
@ -61,11 +62,11 @@ public class LiveObjectInterceptor {
this.codecProvider = codecProvider;
this.originalClass = entityClass;
this.idFieldName = idFieldName;
REntity anno = (REntity) entityClass.getAnnotation(REntity.class);
REntity anno = (REntity) ClassUtils.getAnnotation(entityClass, REntity.class);
this.codecClass = anno.codec();
try {
this.namingScheme = anno.namingScheme().getDeclaredConstructor(Codec.class).newInstance(codecProvider.getCodec(anno, originalClass));
this.idFieldType = originalClass.getDeclaredField(idFieldName).getType();
this.idFieldType = ClassUtils.getDeclaredField(originalClass, idFieldName).getType();
} catch (Exception e) {
throw new IllegalArgumentException(e);
}

@ -44,6 +44,7 @@ import org.redisson.api.annotation.REntity;
import org.redisson.api.annotation.RObjectField;
import org.redisson.client.codec.Codec;
import org.redisson.codec.CodecProvider;
import org.redisson.liveobject.misc.ClassUtils;
import org.redisson.liveobject.resolver.NamingScheme;
import org.redisson.misc.RedissonObjectFactory;
@ -116,12 +117,12 @@ public class RedissonObjectBuilder {
* WARNING: rEntity has to be the class of @This object.
*/
private Codec getFieldCodec(Class<?> rEntity, Class<? extends RObject> rObjectClass, String fieldName) throws Exception {
Field field = rEntity.getDeclaredField(fieldName);
Field field = ClassUtils.getDeclaredField(rEntity, fieldName);
if (field.isAnnotationPresent(RObjectField.class)) {
RObjectField anno = field.getAnnotation(RObjectField.class);
return codecProvider.getCodec(anno, rEntity, rObjectClass, fieldName);
} else {
REntity anno = rEntity.getAnnotation(REntity.class);
REntity anno = ClassUtils.getAnnotation(rEntity, REntity.class);
return codecProvider.getCodec(anno, (Class<?>) rEntity);
}
}
@ -131,7 +132,7 @@ public class RedissonObjectBuilder {
*/
private NamingScheme getFieldNamingScheme(Class<?> rEntity, String fieldName, Codec c) throws Exception {
if (!namingSchemeCache.containsKey(fieldName)) {
REntity anno = rEntity.getAnnotation(REntity.class);
REntity anno = ClassUtils.getAnnotation(rEntity, REntity.class);
namingSchemeCache.putIfAbsent(fieldName, anno.namingScheme()
.getDeclaredConstructor(Codec.class)
.newInstance(c));

@ -45,9 +45,15 @@
package org.redisson.liveobject.misc;
import org.redisson.api.RObject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
*
@ -57,7 +63,7 @@ public class ClassUtils {
public static void setField(Object obj, String fieldName, Object value) {
try {
Field field = obj.getClass().getDeclaredField(fieldName);
Field field = getDeclaredField(obj.getClass(), fieldName);
if (!field.isAccessible()) {
field.setAccessible(true);
}
@ -69,7 +75,7 @@ public class ClassUtils {
public static <T extends Annotation> T getAnnotation(Class<?> clazz, String fieldName, Class<T> annotationClass) {
try {
Field field = clazz.getDeclaredField(fieldName);
Field field = getDeclaredField(clazz, fieldName);
if (!field.isAccessible()) {
field.setAccessible(true);
}
@ -78,10 +84,19 @@ public class ClassUtils {
return null;
}
}
public static <T extends Annotation> T getAnnotation(Class<?> clazz, Class<T> annotationClass) {
for (Class<?> c : getClassHierarchy(clazz)) {
if (c.getAnnotation(annotationClass) != null) {
return c.getAnnotation(annotationClass);
}
}
return null;
}
public static <T> T getField(Object obj, String fieldName) {
try {
Field field = obj.getClass().getDeclaredField(fieldName);
Field field = getDeclaredField(obj.getClass(), fieldName);
if (!field.isAccessible()) {
field.setAccessible(true);
}
@ -90,6 +105,38 @@ public class ClassUtils {
throw new IllegalArgumentException(e);
}
}
public static Field getDeclaredField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
for (Class c : getClassHierarchy(clazz)) {
for (Field field : c.getDeclaredFields()) {
if (field.getName().equals(fieldName)) {
return field;
}
}
}
throw new NoSuchFieldException("No such field: " + fieldName);
}
public static boolean isAnnotationPresent(Class<?> clazz, Class<? extends Annotation> annotation) {
for (Class<?> c : getClassHierarchy(clazz)) {
if (c.isAnnotationPresent(annotation)) {
return true;
}
}
return false;
}
private static Iterable<Class<?>> getClassHierarchy(Class<?> clazz) {
// Don't descend into hierarchy for RObjects
if (Arrays.asList(clazz.getInterfaces()).contains(RObject.class)) {
return Collections.singleton(clazz);
}
List<Class<?>> classes = new ArrayList<>();
for (Class c = clazz; c != null; c = c.getSuperclass()) {
classes.add(c);
}
return classes;
}
/**
* Searches through all methods looking for one with the specified name that

@ -16,6 +16,11 @@
package org.redisson.liveobject.misc;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
@ -59,8 +64,15 @@ public class Introspectior {
}
public static FieldList<FieldDescription.InDefinedShape> getFieldsWithAnnotation(Class<?> c, Class<? extends Annotation> a) {
return getTypeDescription(c)
.getDeclaredFields()
return getAllFields(c)
.filter(ElementMatchers.isAnnotatedWith(a));
}
public static FieldList<FieldDescription.InDefinedShape> getAllFields(Class<?> cls) {
List<Field> fields = new ArrayList<>();
for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
Collections.addAll(fields, c.getDeclaredFields());
}
return new FieldList.ForLoadedFields(fields);
}
}

@ -34,6 +34,7 @@ import org.redisson.api.annotation.RId;
import org.redisson.client.codec.Codec;
import org.redisson.codec.CodecProvider;
import org.redisson.config.Config;
import org.redisson.liveobject.misc.ClassUtils;
import org.redisson.liveobject.misc.Introspectior;
import org.redisson.liveobject.resolver.NamingScheme;
@ -104,9 +105,9 @@ public class RedissonObjectFactory {
Class<? extends Object> type = rr.getType();
CodecProvider codecProvider = redisson.getConfig().getCodecProvider();
if (type != null) {
if (type.isAnnotationPresent(REntity.class)) {
if (ClassUtils.isAnnotationPresent(type, REntity.class)) {
RLiveObjectService liveObjectService = redisson.getLiveObjectService();
REntity anno = type.getAnnotation(REntity.class);
REntity anno = ClassUtils.getAnnotation(type, REntity.class);
NamingScheme ns = anno.namingScheme()
.getDeclaredConstructor(Codec.class)
.newInstance(codecProvider.getCodec(anno, type));
@ -151,7 +152,7 @@ public class RedissonObjectFactory {
}
public static RedissonReference toReference(Config config, Object object) {
if (object != null && object.getClass().isAnnotationPresent(REntity.class)) {
if (object != null && ClassUtils.isAnnotationPresent(object.getClass(), REntity.class)) {
throw new IllegalArgumentException("REntity should be attached to Redisson before save");
}
@ -169,14 +170,14 @@ public class RedissonObjectFactory {
try {
if (object instanceof RLiveObject) {
Class<? extends Object> rEntity = object.getClass().getSuperclass();
REntity anno = rEntity.getAnnotation(REntity.class);
REntity anno = ClassUtils.getAnnotation(rEntity, REntity.class);
NamingScheme ns = anno.namingScheme()
.getDeclaredConstructor(Codec.class)
.newInstance(config.getCodecProvider().getCodec(anno, (Class) rEntity));
String name = Introspectior
.getFieldsWithAnnotation(rEntity, RId.class)
.getOnly().getName();
Class<?> type = rEntity.getDeclaredField(name).getType();
Class<?> type = ClassUtils.getDeclaredField(rEntity, name).getType();
return new RedissonReference(rEntity,
ns.getName(rEntity, type, name, ((RLiveObject) object).getLiveObjectId()));
}

@ -1129,11 +1129,9 @@ public class RedissonLiveObjectServiceTest extends BaseTest {
SimpleObject s = new SimpleObject();
s = service.persist(s);
so.setSo(s);
assertThat(s.getId()).isNotNull();
so.getObjects().add(s);
so = redisson.getLiveObjectService().detach(so);
assertThat(so.getSo().getId()).isEqualTo(s.getId());
assertThat(so.getObjects().get(0).getId()).isEqualTo(so.getSo().getId());
@ -1462,5 +1460,158 @@ public class RedissonLiveObjectServiceTest extends BaseTest {
sg = redisson.getLiveObjectService().persist(sg);
assertThat(sg.getName()).isEqualTo("1234");
}
@REntity
public static class Animal {
@RId(generator = LongGenerator.class)
private Long id;
private String name;
protected Animal() {
}
public Animal(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
}
public static class Dog extends Animal {
private String breed;
public Dog(String name) {
super(name);
}
protected Dog() {
}
public void setBreed(String breed) {
this.breed = breed;
}
public String getBreed() {
return breed;
}
}
@Test
public void testInheritedREntity() {
Dog d = new Dog("Fido");
d.setBreed("lab");
d = redisson.getLiveObjectService().persist(d);
assertThat(d.getName()).isEqualTo("Fido");
assertThat(d.getBreed()).isEqualTo("lab");
}
@Test
public void testMapOfInheritedEntity() {
RMap<String, Dog> dogs = redisson.getMap("dogs");
Dog d = new Dog("Fido");
d = redisson.getLiveObjectService().persist(d);
d.setBreed("lab");
dogs.put("key", d);
dogs = redisson.getMap("dogs");
assertThat(dogs.size()).isEqualTo(1);
assertThat(dogs.get("key").getBreed()).isEqualTo("lab");
}
public static class MyCustomer extends Customer {
@RCascade(RCascadeType.ALL)
private List<Order> specialOrders = new ArrayList<>();
public MyCustomer() {
}
public MyCustomer(String id) {
super(id);
}
public void addSpecialOrder(Order order) {
getSpecialOrders().add(order);
}
public void setSpecialOrders(List<Order> orders) {
this.specialOrders = orders;
}
public List<Order> getSpecialOrders() {
return specialOrders;
}
}
@Test
public void testCyclicRefsWithInheritedREntity() {
MyCustomer customer = new MyCustomer("12");
customer = redisson.getLiveObjectService().persist(customer);
Order order = new Order();
order = redisson.getLiveObjectService().persist(order);
order.setCustomer(customer);
customer.getOrders().add(order);
Order special = new Order();
special = redisson.getLiveObjectService().persist(special);
order.setCustomer(customer);
customer.addSpecialOrder(special);
customer = redisson.getLiveObjectService().detach(customer);
assertThat(customer.getClass()).isSameAs(MyCustomer.class);
assertThat(customer.getId()).isNotNull();
List<Order> orders = customer.getOrders();
assertThat(orders.get(0)).isNotNull();
List<Order> specials = customer.getSpecialOrders();
assertThat(specials.get(0)).isNotNull();
customer = redisson.getLiveObjectService().get(MyCustomer.class, customer.getId());
assertThat(customer.getId()).isNotNull();
assertThat(customer.getOrders().get(0)).isNotNull();
assertThat(customer.getSpecialOrders().get(0)).isNotNull();
}
public static class MyObjectWithList extends ObjectWithList {
protected MyObjectWithList() {
super();
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Test
public void testStoreInnerObjectWithInheritedREntity() {
RLiveObjectService service = redisson.getLiveObjectService();
MyObjectWithList so = new MyObjectWithList();
so = service.persist(so);
SimpleObject s = new SimpleObject();
s = service.persist(s);
so.setSo(s);
assertThat(s.getId()).isNotNull();
so.getObjects().add(s);
so.setName("name");
so = redisson.getLiveObjectService().detach(so);
assertThat(so.getSo().getId()).isEqualTo(s.getId());
assertThat(so.getObjects().get(0).getId()).isEqualTo(so.getSo().getId());
assertThat(so.getName()).isEqualTo("name");
}
}

Loading…
Cancel
Save