Skip to content

Commit

Permalink
Introduce RCascade annotation for LiveObject #651
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita committed Oct 4, 2016
1 parent f2957b3 commit 3ac7eae
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 24 deletions.
86 changes: 64 additions & 22 deletions redisson/src/main/java/org/redisson/RedissonLiveObjectService.java
Expand Up @@ -18,6 +18,7 @@
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Deque; import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
Expand All @@ -33,6 +34,7 @@
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;


import org.redisson.api.RCascadeType;
import org.redisson.api.RDeque; import org.redisson.api.RDeque;
import org.redisson.api.RExpirable; import org.redisson.api.RExpirable;
import org.redisson.api.RExpirableAsync; import org.redisson.api.RExpirableAsync;
Expand All @@ -47,6 +49,7 @@
import org.redisson.api.RSet; import org.redisson.api.RSet;
import org.redisson.api.RSortedSet; import org.redisson.api.RSortedSet;
import org.redisson.api.RedissonClient; import org.redisson.api.RedissonClient;
import org.redisson.api.annotation.RCascade;
import org.redisson.api.annotation.REntity; import org.redisson.api.annotation.REntity;
import org.redisson.api.annotation.RFieldAccessor; import org.redisson.api.annotation.RFieldAccessor;
import org.redisson.api.annotation.RId; import org.redisson.api.annotation.RId;
Expand Down Expand Up @@ -176,16 +179,16 @@ public <T> T attach(T detachedObject) {
@Override @Override
public <T> T merge(T detachedObject) { public <T> T merge(T detachedObject) {
Map<Object, Object> alreadyPersisted = new HashMap<Object, Object>(); Map<Object, Object> alreadyPersisted = new HashMap<Object, Object>();
return persist(detachedObject, alreadyPersisted, false); return persist(detachedObject, alreadyPersisted, RCascadeType.MERGE);
} }


@Override @Override
public <T> T persist(T detachedObject) { public <T> T persist(T detachedObject) {
Map<Object, Object> alreadyPersisted = new HashMap<Object, Object>(); Map<Object, Object> alreadyPersisted = new HashMap<Object, Object>();
return persist(detachedObject, alreadyPersisted, true); return persist(detachedObject, alreadyPersisted, RCascadeType.PERSIST);
} }


private <T> T persist(T detachedObject, Map<Object, Object> alreadyPersisted, boolean checkExistence) { private <T> T persist(T detachedObject, Map<Object, Object> alreadyPersisted, RCascadeType type) {
String idFieldName = getRIdFieldName(detachedObject.getClass()); String idFieldName = getRIdFieldName(detachedObject.getClass());
Object id = ClassUtils.getField(detachedObject, idFieldName); Object id = ClassUtils.getField(detachedObject, idFieldName);
if (id == null) { if (id == null) {
Expand All @@ -204,7 +207,7 @@ private <T> T persist(T detachedObject, Map<Object, Object> alreadyPersisted, bo
List<String> excludedFields = new ArrayList<String>(); List<String> excludedFields = new ArrayList<String>();
excludedFields.add(idFieldName); excludedFields.add(idFieldName);
boolean fastResult = liveMap.fastPut("redisson_live_object", "1"); boolean fastResult = liveMap.fastPut("redisson_live_object", "1");
if (checkExistence && !fastResult) { if (type == RCascadeType.PERSIST && !fastResult) {
throw new IllegalArgumentException("This REntity already exists."); throw new IllegalArgumentException("This REntity already exists.");
} }


Expand All @@ -218,25 +221,26 @@ private <T> T persist(T detachedObject, Map<Object, Object> alreadyPersisted, bo
RObject rObject = objectBuilder.createObject(id, detachedObject.getClass(), object.getClass(), field.getName(), null); RObject rObject = objectBuilder.createObject(id, detachedObject.getClass(), object.getClass(), field.getName(), null);
if (rObject != null) { if (rObject != null) {
objectBuilder.store(rObject, field.getName(), liveMap); objectBuilder.store(rObject, field.getName(), liveMap);

if (rObject instanceof SortedSet) { if (rObject instanceof SortedSet) {
((RSortedSet)rObject).trySetComparator(((SortedSet)object).comparator()); ((RSortedSet)rObject).trySetComparator(((SortedSet)object).comparator());
} }


if (!checkCascade(detachedObject, type, field.getName())) {
continue;
}

if (rObject instanceof Collection) { if (rObject instanceof Collection) {
for (Object obj : (Collection<Object>)object) { for (Object obj : (Collection<Object>)object) {
if (obj != null && obj.getClass().isAnnotationPresent(REntity.class)) { if (obj != null && obj.getClass().isAnnotationPresent(REntity.class)) {
Object persisted = alreadyPersisted.get(obj); Object persisted = alreadyPersisted.get(obj);
if (persisted == null) { if (persisted == null) {
persisted = persist(obj, alreadyPersisted, checkExistence); persisted = persist(obj, alreadyPersisted, type);
} }
obj = persisted; obj = persisted;
} }
((Collection)rObject).add(obj); ((Collection)rObject).add(obj);
} }
} } else if (rObject instanceof Map) {

if (rObject instanceof Map) {
Map<Object, Object> rMap = (Map<Object, Object>) rObject; Map<Object, Object> rMap = (Map<Object, Object>) rObject;
Map<?, ?> map = (Map<?, ?>)rObject; Map<?, ?> map = (Map<?, ?>)rObject;
for (Entry<?, ?> entry : map.entrySet()) { for (Entry<?, ?> entry : map.entrySet()) {
Expand All @@ -246,15 +250,15 @@ private <T> T persist(T detachedObject, Map<Object, Object> alreadyPersisted, bo
if (key != null && key.getClass().isAnnotationPresent(REntity.class)) { if (key != null && key.getClass().isAnnotationPresent(REntity.class)) {
Object persisted = alreadyPersisted.get(key); Object persisted = alreadyPersisted.get(key);
if (persisted == null) { if (persisted == null) {
persisted = persist(key, alreadyPersisted, checkExistence); persisted = persist(key, alreadyPersisted, type);
} }
key = persisted; key = persisted;
} }


if (value != null && value.getClass().isAnnotationPresent(REntity.class)) { if (value != null && value.getClass().isAnnotationPresent(REntity.class)) {
Object persisted = alreadyPersisted.get(value); Object persisted = alreadyPersisted.get(value);
if (persisted == null) { if (persisted == null) {
persisted = persist(value, alreadyPersisted, checkExistence); persisted = persist(value, alreadyPersisted, type);
} }
value = persisted; value = persisted;
} }
Expand All @@ -263,21 +267,50 @@ private <T> T persist(T detachedObject, Map<Object, Object> alreadyPersisted, bo
} }
} }
excludedFields.add(field.getName()); excludedFields.add(field.getName());
} } else if (object.getClass().isAnnotationPresent(REntity.class)) {

if (object.getClass().isAnnotationPresent(REntity.class)) {
Object persisted = alreadyPersisted.get(object); Object persisted = alreadyPersisted.get(object);
if (persisted == null) { if (persisted == null) {
persisted = persist(object, alreadyPersisted, checkExistence); if (checkCascade(detachedObject, type, field.getName())) {
persisted = persist(object, alreadyPersisted, type);
}
} }


excludedFields.add(field.getName()); excludedFields.add(field.getName());
BeanUtil.pojo.setSimpleProperty(attachedObject, field.getName(), persisted); BeanUtil.pojo.setSimpleProperty(attachedObject, field.getName(), persisted);
} else {
validateAnnotation(detachedObject, field.getName());
} }

} }
copy(detachedObject, attachedObject, excludedFields); copy(detachedObject, attachedObject, excludedFields);
return attachedObject; return attachedObject;
} }

private void validateAnnotation(Object instance, String fieldName) {
Class<?> clazz = instance.getClass();
if (isLiveObject(instance)) {
clazz = clazz.getSuperclass();
}

RCascade annotation = ClassUtils.getAnnotation(clazz, fieldName, RCascade.class);
if (annotation != null) {
throw new IllegalArgumentException("RCascade annotation couldn't be defined for non-Redisson object field");
}
}

private <T> boolean checkCascade(Object instance, RCascadeType type, String fieldName) {
Class<?> clazz = instance.getClass();
if (isLiveObject(instance)) {
clazz = clazz.getSuperclass();
}

RCascade annotation = ClassUtils.getAnnotation(clazz, fieldName, RCascade.class);
if (annotation != null && (Arrays.asList(annotation.value()).contains(type)
|| Arrays.asList(annotation.value()).contains(RCascadeType.ALL))) {
return true;
}
return false;
}


@Override @Override
public <T> T detach(T attachedObject) { public <T> T detach(T attachedObject) {
Expand All @@ -294,6 +327,10 @@ private <T> T detach(T attachedObject, Map<String, Object> alreadyDetached) {
alreadyDetached.put(getMap(attachedObject).getName(), detached); alreadyDetached.put(getMap(attachedObject).getName(), detached);


for (Entry<String, Object> obj : getMap(attachedObject).entrySet()) { for (Entry<String, Object> obj : getMap(attachedObject).entrySet()) {
if (!checkCascade(attachedObject, RCascadeType.DETACH, obj.getKey())) {
continue;
}

if (obj.getValue() instanceof RSortedSet) { if (obj.getValue() instanceof RSortedSet) {
SortedSet<Object> redissonSet = (SortedSet<Object>) obj.getValue(); SortedSet<Object> redissonSet = (SortedSet<Object>) obj.getValue();
Set<Object> set = new TreeSet<Object>(redissonSet.comparator()); Set<Object> set = new TreeSet<Object>(redissonSet.comparator());
Expand Down Expand Up @@ -369,9 +406,7 @@ private <T> T detach(T attachedObject, Map<String, Object> alreadyDetached) {
} }


ClassUtils.setField(detached, obj.getKey(), list); ClassUtils.setField(detached, obj.getKey(), list);
} } else if (isLiveObject(obj.getValue())) {

if (isLiveObject(obj.getValue())) {
Object detachedObject = alreadyDetached.get(getMap(obj.getValue()).getName()); Object detachedObject = alreadyDetached.get(getMap(obj.getValue()).getName());
if (detachedObject == null) { if (detachedObject == null) {
detachedObject = detach(obj.getValue(), alreadyDetached); detachedObject = detach(obj.getValue(), alreadyDetached);
Expand Down Expand Up @@ -403,6 +438,8 @@ private <T> T detach(T attachedObject, Map<String, Object> alreadyDetached) {
} }


ClassUtils.setField(detached, obj.getKey(), map); ClassUtils.setField(detached, obj.getKey(), map);
} else {
validateAnnotation(detached, obj.getKey());
} }
} }


Expand All @@ -418,10 +455,14 @@ public <T> void delete(T attachedObject) {
delete(attachedObject, deleted); delete(attachedObject, deleted);
} }


public <T> void delete(T attachedObject, Set<String> deleted) { private <T> void delete(T attachedObject, Set<String> deleted) {
validateAttached(attachedObject); validateAttached(attachedObject);


for (Entry<String, Object> obj : getMap(attachedObject).entrySet()) { for (Entry<String, Object> obj : getMap(attachedObject).entrySet()) {
if (!checkCascade(attachedObject, RCascadeType.DELETE, obj.getKey())) {
continue;
}

if (obj.getValue() instanceof RSortedSet) { if (obj.getValue() instanceof RSortedSet) {
deleteCollection(deleted, (Iterable<?>)obj.getValue()); deleteCollection(deleted, (Iterable<?>)obj.getValue());
((RObject)obj.getValue()).delete(); ((RObject)obj.getValue()).delete();
Expand All @@ -437,9 +478,7 @@ public <T> void delete(T attachedObject, Set<String> deleted) {
} else if (obj.getValue() instanceof RList) { } else if (obj.getValue() instanceof RList) {
deleteCollection(deleted, (Iterable<?>)obj.getValue()); deleteCollection(deleted, (Iterable<?>)obj.getValue());
((RObject)obj.getValue()).delete(); ((RObject)obj.getValue()).delete();
} } else if (isLiveObject(obj.getValue())) {

if (isLiveObject(obj.getValue())) {
if (deleted.add(getMap(obj.getValue()).getName())) { if (deleted.add(getMap(obj.getValue()).getName())) {
delete(obj.getValue(), deleted); delete(obj.getValue(), deleted);
} }
Expand All @@ -448,7 +487,10 @@ public <T> void delete(T attachedObject, Set<String> deleted) {
deleteCollection(deleted, map.keySet()); deleteCollection(deleted, map.keySet());
deleteCollection(deleted, map.values()); deleteCollection(deleted, map.values());
((RObject)obj.getValue()).delete(); ((RObject)obj.getValue()).delete();
} else {
validateAnnotation(attachedObject, obj.getKey());
} }

} }
asLiveObject(attachedObject).delete(); asLiveObject(attachedObject).delete();
} }
Expand Down
32 changes: 32 additions & 0 deletions redisson/src/main/java/org/redisson/api/RCascadeType.java
@@ -0,0 +1,32 @@
/**
* Copyright 2016 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.api;

/**
*
* @author Nikita Koksharov
*
*/
public enum RCascadeType {

ALL,
PERSIST,
DETACH,
MERGE,
DELETE


}
36 changes: 36 additions & 0 deletions redisson/src/main/java/org/redisson/api/annotation/RCascade.java
@@ -0,0 +1,36 @@
/**
* Copyright 2016 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.api.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.redisson.api.RCascadeType;

/**
*
* @author Nikita Koksharov
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface RCascade {

RCascadeType[] value();

}
Expand Up @@ -92,7 +92,7 @@ public Object intercept(@Origin Method method, @SuperCall Callable<?> superMetho
} }
if (isSetter(method, fieldName)) { if (isSetter(method, fieldName)) {
Object arg = args[0]; Object arg = args[0];
if (arg.getClass().isAnnotationPresent(REntity.class)) { if (arg != null && arg.getClass().isAnnotationPresent(REntity.class)) {
throw new IllegalStateException("REntity object should be attached to Redisson first"); throw new IllegalStateException("REntity object should be attached to Redisson first");
} }


Expand Down Expand Up @@ -125,7 +125,11 @@ public Object intercept(@Origin Method method, @SuperCall Callable<?> superMetho
objectBuilder.store((RObject)arg, fieldName, liveMap); objectBuilder.store((RObject)arg, fieldName, liveMap);
return me; return me;
} }
liveMap.fastPut(fieldName, args[0]); if (arg == null) {
liveMap.remove(fieldName);
} else {
liveMap.fastPut(fieldName, arg);
}
return me; return me;
} }
return superMethod.call(); return superMethod.call();
Expand Down
Expand Up @@ -45,6 +45,7 @@


package org.redisson.liveobject.misc; package org.redisson.liveobject.misc;


import java.lang.annotation.Annotation;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;


Expand All @@ -66,6 +67,18 @@ public static void setField(Object obj, String fieldName, Object value) {
} }
} }


public static <T extends Annotation> T getAnnotation(Class<?> clazz, String fieldName, Class<T> annotationClass) {
try {
Field field = clazz.getDeclaredField(fieldName);
if (!field.isAccessible()) {
field.setAccessible(true);
}
return field.getAnnotation(annotationClass);
} catch (NoSuchFieldException e) {
return null;
}
}

public static <T> T getField(Object obj, String fieldName) { public static <T> T getField(Object obj, String fieldName) {
try { try {
Field field = obj.getClass().getDeclaredField(fieldName); Field field = obj.getClass().getDeclaredField(fieldName);
Expand Down
Expand Up @@ -28,6 +28,7 @@
import org.junit.Test; import org.junit.Test;
import org.redisson.api.RBlockingDeque; import org.redisson.api.RBlockingDeque;
import org.redisson.api.RBlockingQueue; import org.redisson.api.RBlockingQueue;
import org.redisson.api.RCascadeType;
import org.redisson.api.RDeque; import org.redisson.api.RDeque;
import org.redisson.api.RList; import org.redisson.api.RList;
import org.redisson.api.RLiveObject; import org.redisson.api.RLiveObject;
Expand All @@ -38,6 +39,7 @@
import org.redisson.api.RSet; import org.redisson.api.RSet;
import org.redisson.api.RSortedSet; import org.redisson.api.RSortedSet;
import org.redisson.api.RedissonClient; import org.redisson.api.RedissonClient;
import org.redisson.api.annotation.RCascade;
import org.redisson.api.annotation.REntity; import org.redisson.api.annotation.REntity;
import org.redisson.api.annotation.RFieldAccessor; import org.redisson.api.annotation.RFieldAccessor;
import org.redisson.api.annotation.RId; import org.redisson.api.annotation.RId;
Expand Down Expand Up @@ -387,6 +389,7 @@ public static class TestClass {


private String value; private String value;
private String code; private String code;
@RCascade(RCascadeType.ALL)
private Object content; private Object content;


@RId(generator = RandomUUIDIdStringGenerator.class) @RId(generator = RandomUUIDIdStringGenerator.class)
Expand Down Expand Up @@ -1200,6 +1203,7 @@ public static class Customer {
@RId @RId
private String id; private String id;


@RCascade(RCascadeType.ALL)
private List<Order> orders = new ArrayList<>(); private List<Order> orders = new ArrayList<>();


public Customer() { public Customer() {
Expand Down Expand Up @@ -1234,6 +1238,7 @@ public static class Order {
@RId(generator = DistributedAtomicLongIdGenerator.class) @RId(generator = DistributedAtomicLongIdGenerator.class)
private Long id; private Long id;


@RCascade({RCascadeType.PERSIST, RCascadeType.DETACH})
private Customer customer; private Customer customer;


public Order() { public Order() {
Expand Down Expand Up @@ -1295,7 +1300,12 @@ public void testDelete() {
Order order = new Order(customer); Order order = new Order(customer);
order = redisson.getLiveObjectService().persist(order); order = redisson.getLiveObjectService().persist(order);
assertThat(redisson.getKeys().count()).isEqualTo(3); assertThat(redisson.getKeys().count()).isEqualTo(3);

Customer persistedCustomer = order.getCustomer();
redisson.getLiveObjectService().delete(order); redisson.getLiveObjectService().delete(order);
assertThat(redisson.getKeys().count()).isEqualTo(2);

redisson.getLiveObjectService().delete(persistedCustomer);
assertThat(redisson.getKeys().count()).isEqualTo(1); assertThat(redisson.getKeys().count()).isEqualTo(1);
} }


Expand Down

0 comments on commit 3ac7eae

Please sign in to comment.