diff --git a/src/main/java/org/redisson/RedissonLiveObjectService.java b/src/main/java/org/redisson/RedissonLiveObjectService.java index 8b1c268f6..b0f7a8b03 100644 --- a/src/main/java/org/redisson/RedissonLiveObjectService.java +++ b/src/main/java/org/redisson/RedissonLiveObjectService.java @@ -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 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 classCache, CodecProvider codecProvider) { this.redisson = redisson; @@ -74,9 +71,9 @@ public class RedissonLiveObjectService implements RLiveObjectService { Class entityClass = (Class) detachedObject.getClass(); try { Class 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 detach(T attachedObject) { try { - //deep copy - return objectMapper.convertValue(attachedObject, (Class)attachedObject.getClass().getSuperclass()); + T detached = instantiateDetachedObject((Class) 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 instantiateDetachedObject(Class 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 instantiate(Class 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 Class getProxyClass(Class entityClass) throws Exception { if (!classCache.containsKey(entityClass)) { validateClass(entityClass); @@ -201,7 +210,8 @@ public class RedissonLiveObjectService implements RLiveObjectService { .getDeclaredFields()) { builder = builder.define(field); } - Class loaded = builder.method(ElementMatchers.isDeclaredBy( + + Class 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) { diff --git a/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java b/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java index 661649b36..0f472523c 100644 --- a/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java +++ b/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java @@ -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 static 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 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()); - } } diff --git a/src/main/java/org/redisson/liveobject/misc/RedissonObjectFactory.java b/src/main/java/org/redisson/liveobject/misc/RedissonObjectFactory.java new file mode 100644 index 000000000..344d90457 --- /dev/null +++ b/src/main/java/org/redisson/liveobject/misc/RedissonObjectFactory.java @@ -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 create(RedissonClient redisson, CodecProvider codecProvider, RedissonReference rr, Class expected) throws Exception { + Class 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()); + } +} diff --git a/src/test/java/org/redisson/RedissonAttachedLiveObjectServiceTest.java b/src/test/java/org/redisson/RedissonAttachedLiveObjectServiceTest.java index e801952c5..993a367bb 100644 --- a/src/test/java/org/redisson/RedissonAttachedLiveObjectServiceTest.java +++ b/src/test/java/org/redisson/RedissonAttachedLiveObjectServiceTest.java @@ -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 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); + } + + }