Completed LiveObject API with tests

pull/527/head
jackygurui 9 years ago
parent 618d2cbd65
commit f36bade5c1

@ -1,11 +1,7 @@
package org.redisson;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import jodd.bean.BeanCopy;
import jodd.bean.BeanUtil;
//import java.util.concurrent.TimeUnit;
@ -17,7 +13,6 @@ import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.FieldProxy;
import net.bytebuddy.matcher.ElementMatchers;
import org.redisson.codec.JsonJacksonCodec;
//import org.redisson.core.RExpirable;
//import org.redisson.core.RExpirableAsync;
//import org.redisson.core.RMap;
@ -38,8 +33,10 @@ public class RedissonLiveObjectService implements RLiveObjectService {
private final Map<Class, Class> classCache;
private final RedissonClient redisson;
private final ObjectMapper objectMapper = JsonJacksonCodec.INSTANCE.getObjectMapper();
// private final ObjectMapper detachMapper;
// private final ObjectMapper attachMapper;
private final CodecProvider codecProvider;
// private final SimpleModule module;
public RedissonLiveObjectService(RedissonClient redisson, Map<Class, Class> classCache, CodecProvider codecProvider) {
this.redisson = redisson;
@ -74,9 +71,9 @@ public class RedissonLiveObjectService implements RLiveObjectService {
Class<T> entityClass = (Class<T>) detachedObject.getClass();
try {
Class<? extends T> proxyClass = getProxyClass(entityClass);
String idFieldName = getRIdFieldName(detachedObject.getClass());
return instantiateLiveObject(proxyClass,
BeanUtil.pojo.getSimpleProperty(detachedObject, idFieldName));
BeanUtil.pojo.getSimpleProperty(detachedObject,
getRIdFieldName(detachedObject.getClass())));
} catch (Exception ex) {
unregisterClass(entityClass);
throw new RuntimeException(ex);
@ -103,10 +100,11 @@ public class RedissonLiveObjectService implements RLiveObjectService {
@Override
public <T> T detach(T attachedObject) {
try {
//deep copy
return objectMapper.<T>convertValue(attachedObject, (Class<T>)attachedObject.getClass().getSuperclass());
T detached = instantiateDetachedObject((Class<T>) attachedObject.getClass().getSuperclass(), asLiveObject(attachedObject).getLiveObjectId());
BeanCopy.beans(attachedObject, detached).declared(false, true).copy();
return detached;
} catch (Exception ex) {
throw new IllegalArgumentException(ex);
throw new RuntimeException(ex);
}
}
@ -137,7 +135,7 @@ public class RedissonLiveObjectService implements RLiveObjectService {
.exclude(idFieldName)
.copy();
}
private String getRIdFieldName(Class cls) {
return Introspectior.getFieldsWithAnnotation(cls, RId.class)
.getOnly()
@ -149,17 +147,28 @@ public class RedissonLiveObjectService implements RLiveObjectService {
asLiveObject(instance).setLiveObjectId(id);
return instance;
}
private <T, K> T instantiateDetachedObject(Class<T> cls, K id) throws Exception {
T instance = instantiate(cls, id);
if (BeanUtil.pojo.getSimpleProperty(instance, getRIdFieldName(cls)) == null) {
BeanUtil.pojo.setSimpleProperty(instance, getRIdFieldName(cls), id);
}
return instance;
}
private <T, K> T instantiate(Class<T> cls, K id) throws Exception {
T instance;
try {
instance = cls.newInstance();
return cls.newInstance();
} catch (Exception exception) {
instance = cls.getDeclaredConstructor(id.getClass()).newInstance(id);
for (Constructor<?> ctor : classCache.containsKey(cls) ? cls.getConstructors() : cls.getDeclaredConstructors()) {
if (ctor.getParameterCount() == 1 && ctor.getParameterTypes()[0].isAssignableFrom(id.getClass())) {
return (T) ctor.newInstance(id);
}
}
}
return instance;
throw new NoSuchMethodException("Unable to find constructor matching only the RId field.");
}
private <T> Class<? extends T> getProxyClass(Class<T> entityClass) throws Exception {
if (!classCache.containsKey(entityClass)) {
validateClass(entityClass);
@ -201,7 +210,8 @@ public class RedissonLiveObjectService implements RLiveObjectService {
.getDeclaredFields()) {
builder = builder.define(field);
}
Class<? extends T> loaded = builder.method(ElementMatchers.isDeclaredBy(
Class<? extends T> proxied = builder.method(ElementMatchers.isDeclaredBy(
Introspectior.getTypeDescription(RLiveObject.class))
.and(ElementMatchers.isGetter().or(ElementMatchers.isSetter())))
.intercept(MethodDelegation.to(
@ -211,18 +221,18 @@ public class RedissonLiveObjectService implements RLiveObjectService {
.install(LiveObjectInterceptor.Getter.class,
LiveObjectInterceptor.Setter.class)))
.implement(RLiveObject.class)
// .method(ElementMatchers.isDeclaredBy(RExpirable.class)
// .or(ElementMatchers.isDeclaredBy(RExpirableAsync.class))
// .or(ElementMatchers.isDeclaredBy(RObject.class))
// .or(ElementMatchers.isDeclaredBy(RObjectAsync.class)))
// .intercept(MethodDelegation.to(ExpirableInterceptor.class))
// .implement(RExpirable.class)
// .method(ElementMatchers.isDeclaredBy(RExpirable.class)
// .or(ElementMatchers.isDeclaredBy(RExpirableAsync.class))
// .or(ElementMatchers.isDeclaredBy(RObject.class))
// .or(ElementMatchers.isDeclaredBy(RObjectAsync.class)))
// .intercept(MethodDelegation.to(ExpirableInterceptor.class))
// .implement(RExpirable.class)
.method(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class))
.and(ElementMatchers.not(ElementMatchers.isDeclaredBy(RLiveObject.class)))
// .and(ElementMatchers.not(ElementMatchers.isDeclaredBy(RExpirable.class)))
// .and(ElementMatchers.not(ElementMatchers.isDeclaredBy(RExpirableAsync.class)))
// .and(ElementMatchers.not(ElementMatchers.isDeclaredBy(RObject.class)))
// .and(ElementMatchers.not(ElementMatchers.isDeclaredBy(RObjectAsync.class)))
// .and(ElementMatchers.not(ElementMatchers.isDeclaredBy(RExpirable.class)))
// .and(ElementMatchers.not(ElementMatchers.isDeclaredBy(RExpirableAsync.class)))
// .and(ElementMatchers.not(ElementMatchers.isDeclaredBy(RObject.class)))
// .and(ElementMatchers.not(ElementMatchers.isDeclaredBy(RObjectAsync.class)))
.and(ElementMatchers.isGetter()
.or(ElementMatchers.isSetter()))
.and(ElementMatchers.isPublic()))
@ -231,7 +241,7 @@ public class RedissonLiveObjectService implements RLiveObjectService {
.make().load(getClass().getClassLoader(),
ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
classCache.putIfAbsent(entityClass, loaded);
classCache.putIfAbsent(entityClass, proxied);
}
public void unregisterClass(Class cls) {

@ -33,6 +33,7 @@ import org.redisson.liveobject.RLiveObject;
import org.redisson.liveobject.annotation.REntity;
import org.redisson.liveobject.annotation.RId;
import org.redisson.liveobject.misc.Introspectior;
import org.redisson.liveobject.misc.RedissonObjectFactory;
/**
* This class is going to be instantiated and becomes a <b>static</b> field of
@ -66,7 +67,7 @@ public class AccessorInterceptor {
if (isGetter(method, fieldName)) {
Object result = liveMap.get(fieldName);
if (result instanceof RedissonReference) {
return createRedissonObject((RedissonReference) result, method.getReturnType());
return RedissonObjectFactory.create(redisson, codecProvider, (RedissonReference) result, method.getReturnType());
}
return result;
}
@ -114,31 +115,4 @@ public class AccessorInterceptor {
.getName();
}
private Object createRedissonObject(RedissonReference rr, Class expected) throws Exception {
Class<? extends Object> type = rr.getType();
if (type != null) {
if (type.isAnnotationPresent(REntity.class)) {
REntity anno = type.getAnnotation(REntity.class);
REntity.NamingScheme ns = anno.namingScheme()
.getDeclaredConstructor(Codec.class)
.newInstance(codecProvider.getCodec(anno, rr.getType()));
return (RLiveObject) redisson.getLiveObjectService(codecProvider).get(type, ns.resolveId(rr.getKeyName()));
}
for (Method method : RedissonClient.class.getDeclaredMethods()) {
if (method.getName().startsWith("get")
&& method.getReturnType().isAssignableFrom(type)
&& expected.isAssignableFrom(method.getReturnType())) {
if (rr.isDefaultCodec() && method.getParameterCount() == 1) {
return (RObject) method.invoke(redisson, rr.getKeyName());
} else if (!rr.isDefaultCodec()
&& method.getParameterCount() == 2
&& String.class.equals(method.getParameterTypes()[0])
&& Codec.class.equals(method.getParameterTypes()[1])) {
return (RObject) method.invoke(redisson, rr.getKeyName(), codecProvider.getCodec(rr.getCodecType()));
}
}
}
}
throw new ClassNotFoundException("No RObject is found to match class type of " + rr.getTypeName() + " with codec type of " + rr.getCodecName());
}
}

@ -0,0 +1,58 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* 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.liveobject.misc;
import java.lang.reflect.Method;
import org.redisson.RedissonClient;
import org.redisson.RedissonReference;
import org.redisson.client.codec.Codec;
import org.redisson.liveobject.CodecProvider;
import org.redisson.liveobject.annotation.REntity;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class RedissonObjectFactory {
public static <T> T create(RedissonClient redisson, CodecProvider codecProvider, RedissonReference rr, Class expected) throws Exception {
Class<? extends Object> type = rr.getType();
if (type != null) {
if (type.isAnnotationPresent(REntity.class)) {
REntity anno = type.getAnnotation(REntity.class);
REntity.NamingScheme ns = anno.namingScheme()
.getDeclaredConstructor(Codec.class)
.newInstance(codecProvider.getCodec(anno, rr.getType()));
return (T) redisson.getLiveObjectService(codecProvider).get(type, ns.resolveId(rr.getKeyName()));
}
for (Method method : RedissonClient.class.getDeclaredMethods()) {
if (method.getName().startsWith("get")
&& method.getReturnType().isAssignableFrom(type)
&& expected.isAssignableFrom(method.getReturnType())) {
if (rr.isDefaultCodec() && method.getParameterCount() == 1) {
return (T) method.invoke(redisson, rr.getKeyName());
} else if (!rr.isDefaultCodec()
&& method.getParameterCount() == 2
&& String.class.equals(method.getParameterTypes()[0])
&& Codec.class.equals(method.getParameterTypes()[1])) {
return (T) method.invoke(redisson, rr.getKeyName(), codecProvider.getCodec(rr.getCodecType()));
}
}
}
}
throw new ClassNotFoundException("No RObject is found to match class type of " + rr.getTypeName() + " with codec type of " + rr.getCodecName());
}
}

@ -322,12 +322,18 @@ public class RedissonAttachedLiveObjectServiceTest extends BaseTest {
}
@REntity
public static class TestSerializable {
public static class TestClass {
private String value;
private String code;
private Object content;
@RId
private Serializable id;
private String value;
public TestClass(Serializable id) {
this.id = id;
}
public Serializable getId() {
return id;
@ -341,6 +347,44 @@ public class RedissonAttachedLiveObjectServiceTest extends BaseTest {
this.value = value;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Object getContent() {
return content;
}
public void setContent(Object content) {
this.content = content;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof TestClass) || !this.getClass().equals(obj.getClass())) {
return false;
}
TestClass o = (TestClass) obj;
return Objects.equals(this.id, o.id)
&& Objects.equals(this.code, o.code)
&& Objects.equals(this.value, o.value)
&& Objects.equals(this.content, o.content);
}
@Override
public int hashCode() {
int hash = 3;
hash = 23 * hash + Objects.hashCode(this.value);
hash = 23 * hash + Objects.hashCode(this.code);
hash = 23 * hash + Objects.hashCode(this.id);
hash = 23 * hash + Objects.hashCode(this.content);
return hash;
}
}
public static class ObjectId implements Serializable {
@ -386,43 +430,93 @@ public class RedissonAttachedLiveObjectServiceTest extends BaseTest {
@Test
public void testSerializerable() {
RLiveObjectService liveObjectService = redisson.getLiveObjectService();
RLiveObjectService service = redisson.getLiveObjectService();
TestSerializable t = liveObjectService.get(TestSerializable.class, "55555");
TestClass t = service.get(TestClass.class, "55555");
assertTrue(Objects.equals("55555", t.getId()));
t = liveObjectService.get(TestSerializable.class, 90909l);
t = service.get(TestClass.class, 90909l);
assertTrue(Objects.equals(90909l, t.getId()));
t = liveObjectService.get(TestSerializable.class, 90909);
t = service.get(TestClass.class, 90909);
assertTrue(Objects.equals(90909, t.getId()));
t = liveObjectService.get(TestSerializable.class, new ObjectId(9090909));
t = service.get(TestClass.class, new ObjectId(9090909));
assertTrue(Objects.equals(new ObjectId(9090909), t.getId()));
t = liveObjectService.get(TestSerializable.class, new Byte("0"));
t = service.get(TestClass.class, new Byte("0"));
assertEquals(new Byte("0"), Byte.valueOf(t.getId().toString()));
t = liveObjectService.get(TestSerializable.class, (byte) 90);
t = service.get(TestClass.class, (byte) 90);
assertEquals((byte) 90, Byte.parseByte(t.getId().toString()));
t = liveObjectService.get(TestSerializable.class, Arrays.asList(1, 2, 3, 4));
t = service.get(TestClass.class, Arrays.asList(1, 2, 3, 4));
List<Integer> l = new ArrayList();
l.addAll(Arrays.asList(1, 2, 3, 4));
assertTrue(l.removeAll((List) t.getId()));
assertTrue(l.isEmpty());
try {
liveObjectService.get(TestSerializable.class, new int[]{1, 2, 3, 4, 5});
service.get(TestClass.class, new int[]{1, 2, 3, 4, 5});
} catch (Exception e) {
assertEquals("RId value cannot be an array.", e.getCause().getMessage());
}
try {
liveObjectService.get(TestSerializable.class, new byte[]{1, 2, 3, 4, 5});
service.get(TestClass.class, new byte[]{1, 2, 3, 4, 5});
} catch (Exception e) {
assertEquals("RId value cannot be an array.", e.getCause().getMessage());
}
}
@Test
public void testPersist() {
RLiveObjectService service = redisson.getLiveObjectService();
TestClass ts = new TestClass(new ObjectId(100));
ts.setValue("VALUE");
TestClass persisted = service.persist(ts);
assertEquals(new ObjectId(100), persisted.getId());
assertEquals("VALUE", persisted.getValue());
try {
service.persist(ts);
} catch (Exception e) {
assertEquals("This REntity already exists.", e.getMessage());
}
}
@Test
public void testMerge() {
RLiveObjectService service = redisson.getLiveObjectService();
TestClass ts = new TestClass(new ObjectId(100));
ts.setValue("VALUE");
TestClass merged = service.merge(ts);
assertEquals(new ObjectId(100), merged.getId());
assertEquals("VALUE", merged.getValue());
try {
service.persist(ts);
} catch (Exception e) {
assertEquals("This REntity already exists.", e.getMessage());
}
ts = new TestClass(new ObjectId(100));
ts.setCode("CODE");
merged = service.merge(ts);
assertNull(ts.getValue());
assertEquals("VALUE", merged.getValue());
assertEquals("CODE", merged.getCode());
}
@Test
public void testDetach() {
RLiveObjectService service = redisson.getLiveObjectService();
TestClass ts = new TestClass(new ObjectId(100));
ts.setValue("VALUE");
ts.setCode("CODE");
TestClass merged = service.merge(ts);
assertEquals("VALUE", merged.getValue());
assertEquals("CODE", merged.getCode());
TestClass detach = service.detach(merged);
assertEquals(ts, detach);
}
}

Loading…
Cancel
Save