From d6979d436b17223b8dbc65e0b5f94800b0227b2c Mon Sep 17 00:00:00 2001 From: Simon Jacobs Date: Wed, 13 Sep 2017 09:14:56 -0400 Subject: [PATCH] Allow LiveObjectService to work with classes that inherit from REntities --- .../redisson/RedissonLiveObjectService.java | 19 +-- .../java/org/redisson/RedissonReference.java | 5 +- .../redisson/codec/DefaultCodecProvider.java | 5 +- .../liveobject/core/AccessorInterceptor.java | 11 +- .../core/LiveObjectInterceptor.java | 5 +- .../core/RedissonObjectBuilder.java | 7 +- .../redisson/liveobject/misc/ClassUtils.java | 55 ++++++- .../liveobject/misc/Introspectior.java | 16 +- .../redisson/misc/RedissonObjectFactory.java | 11 +- .../RedissonLiveObjectServiceTest.java | 155 +++++++++++++++++- 10 files changed, 252 insertions(+), 37 deletions(-) diff --git a/redisson/src/main/java/org/redisson/RedissonLiveObjectService.java b/redisson/src/main/java/org/redisson/RedissonLiveObjectService.java index 6a9c69c5d..86ae2acde 100644 --- a/redisson/src/main/java/org/redisson/RedissonLiveObjectService.java +++ b/redisson/src/main/java/org/redisson/RedissonLiveObjectService.java @@ -112,8 +112,7 @@ public class RedissonLiveObjectService implements RLiveObjectService { private Object generateId(Class 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) { - 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 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)) { diff --git a/redisson/src/main/java/org/redisson/RedissonReference.java b/redisson/src/main/java/org/redisson/RedissonReference.java index a7b52f84d..2421d327d 100644 --- a/redisson/src/main/java/org/redisson/RedissonReference.java +++ b/redisson/src/main/java/org/redisson/RedissonReference.java @@ -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(); diff --git a/redisson/src/main/java/org/redisson/codec/DefaultCodecProvider.java b/redisson/src/main/java/org/redisson/codec/DefaultCodecProvider.java index 1c4dcd6c6..a27c69fcf 100644 --- a/redisson/src/main/java/org/redisson/codec/DefaultCodecProvider.java +++ b/redisson/src/main/java/org/redisson/codec/DefaultCodecProvider.java @@ -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 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.getCodec((Class) anno.codec()); @@ -53,7 +54,7 @@ public class DefaultCodecProvider implements CodecProvider { @Override public T getCodec(RObjectField anno, Class cls, Class 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) { diff --git a/redisson/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java b/redisson/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java index d690b0765..99011040a 100644 --- a/redisson/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java +++ b/redisson/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java @@ -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 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) { diff --git a/redisson/src/main/java/org/redisson/liveobject/core/LiveObjectInterceptor.java b/redisson/src/main/java/org/redisson/liveobject/core/LiveObjectInterceptor.java index e0ae797e3..9b0bbbc58 100644 --- a/redisson/src/main/java/org/redisson/liveobject/core/LiveObjectInterceptor.java +++ b/redisson/src/main/java/org/redisson/liveobject/core/LiveObjectInterceptor.java @@ -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); } diff --git a/redisson/src/main/java/org/redisson/liveobject/core/RedissonObjectBuilder.java b/redisson/src/main/java/org/redisson/liveobject/core/RedissonObjectBuilder.java index b53dc03b3..6835e1078 100644 --- a/redisson/src/main/java/org/redisson/liveobject/core/RedissonObjectBuilder.java +++ b/redisson/src/main/java/org/redisson/liveobject/core/RedissonObjectBuilder.java @@ -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 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)); diff --git a/redisson/src/main/java/org/redisson/liveobject/misc/ClassUtils.java b/redisson/src/main/java/org/redisson/liveobject/misc/ClassUtils.java index e94d91ef0..7e3268449 100644 --- a/redisson/src/main/java/org/redisson/liveobject/misc/ClassUtils.java +++ b/redisson/src/main/java/org/redisson/liveobject/misc/ClassUtils.java @@ -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 getAnnotation(Class clazz, String fieldName, Class 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 getAnnotation(Class clazz, Class annotationClass) { + for (Class c : getClassHierarchy(clazz)) { + if (c.getAnnotation(annotationClass) != null) { + return c.getAnnotation(annotationClass); + } + } + return null; + } + public static 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 annotation) { + for (Class c : getClassHierarchy(clazz)) { + if (c.isAnnotationPresent(annotation)) { + return true; + } + } + return false; + } + + private static Iterable> getClassHierarchy(Class clazz) { + // Don't descend into hierarchy for RObjects + if (Arrays.asList(clazz.getInterfaces()).contains(RObject.class)) { + return Collections.singleton(clazz); + } + List> 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 diff --git a/redisson/src/main/java/org/redisson/liveobject/misc/Introspectior.java b/redisson/src/main/java/org/redisson/liveobject/misc/Introspectior.java index 01c9f9d3a..a6ec3fab0 100644 --- a/redisson/src/main/java/org/redisson/liveobject/misc/Introspectior.java +++ b/redisson/src/main/java/org/redisson/liveobject/misc/Introspectior.java @@ -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 getFieldsWithAnnotation(Class c, Class a) { - return getTypeDescription(c) - .getDeclaredFields() + return getAllFields(c) .filter(ElementMatchers.isAnnotatedWith(a)); } + + public static FieldList getAllFields(Class cls) { + List fields = new ArrayList<>(); + for (Class c = cls; c != null; c = c.getSuperclass()) { + Collections.addAll(fields, c.getDeclaredFields()); + } + return new FieldList.ForLoadedFields(fields); + } } diff --git a/redisson/src/main/java/org/redisson/misc/RedissonObjectFactory.java b/redisson/src/main/java/org/redisson/misc/RedissonObjectFactory.java index 666ceed6a..b379fa249 100644 --- a/redisson/src/main/java/org/redisson/misc/RedissonObjectFactory.java +++ b/redisson/src/main/java/org/redisson/misc/RedissonObjectFactory.java @@ -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 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 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())); } diff --git a/redisson/src/test/java/org/redisson/RedissonLiveObjectServiceTest.java b/redisson/src/test/java/org/redisson/RedissonLiveObjectServiceTest.java index 1659d6e6c..331fb72be 100644 --- a/redisson/src/test/java/org/redisson/RedissonLiveObjectServiceTest.java +++ b/redisson/src/test/java/org/redisson/RedissonLiveObjectServiceTest.java @@ -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 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 specialOrders = new ArrayList<>(); + + public MyCustomer() { + } + + public MyCustomer(String id) { + super(id); + } + + public void addSpecialOrder(Order order) { + getSpecialOrders().add(order); + } + + public void setSpecialOrders(List orders) { + this.specialOrders = orders; + } + + public List 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 orders = customer.getOrders(); + assertThat(orders.get(0)).isNotNull(); + List 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"); + } + + }