From 708d04914535fcc07d8e30fcff18901366ef9cd9 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Wed, 15 Jun 2016 01:16:15 +0100 Subject: [PATCH] Fixed DefaultNamingScheme not serialising RId value properly RId values are now serialised into hex string in DefaultNamingScheme It now throws exception when trying to set RId value as an array. --- .../java/org/redisson/RedissonReference.java | 15 +++++ .../redisson/liveobject/CodecProvider.java | 5 ++ .../liveobject/DefaultCodecProvider.java | 21 ++++++- .../liveobject/annotation/REntity.java | 55 +++++++++++++++++-- .../liveobject/core/AccessorInterceptor.java | 12 +++- .../core/LiveObjectInterceptor.java | 7 ++- ...RedissonAttachedLiveObjectServiceTest.java | 48 ++++++++++++++++ 7 files changed, 152 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/redisson/RedissonReference.java b/src/main/java/org/redisson/RedissonReference.java index 554982fd1..55095bb12 100644 --- a/src/main/java/org/redisson/RedissonReference.java +++ b/src/main/java/org/redisson/RedissonReference.java @@ -1,3 +1,18 @@ +/** + * 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; import org.redisson.client.codec.Codec; diff --git a/src/main/java/org/redisson/liveobject/CodecProvider.java b/src/main/java/org/redisson/liveobject/CodecProvider.java index 8e6fb7920..984560770 100644 --- a/src/main/java/org/redisson/liveobject/CodecProvider.java +++ b/src/main/java/org/redisson/liveobject/CodecProvider.java @@ -17,6 +17,7 @@ package org.redisson.liveobject; import org.redisson.client.codec.Codec; import org.redisson.core.RObject; +import org.redisson.liveobject.annotation.REntity; /** * @@ -25,12 +26,16 @@ import org.redisson.core.RObject; public interface CodecProvider { Codec getCodec(Class codecClass); + + Codec getCodec(REntity anno, Class cls); Codec getCodec(Class codecClass, Class rObjectClass, String name); Codec getCodec(Class codecClass, RObject rObject, String name); void registerCodec(Class codecClass, Codec codec); + + void registerCodec(REntity anno, Class cls, Codec codec); void registerCodec(Class codecClass, Class rObjectClass, String name, Codec codec); diff --git a/src/main/java/org/redisson/liveobject/DefaultCodecProvider.java b/src/main/java/org/redisson/liveobject/DefaultCodecProvider.java index 7bb51a7d3..b32993c01 100644 --- a/src/main/java/org/redisson/liveobject/DefaultCodecProvider.java +++ b/src/main/java/org/redisson/liveobject/DefaultCodecProvider.java @@ -19,6 +19,7 @@ import io.netty.util.internal.PlatformDependent; import java.util.concurrent.ConcurrentMap; import org.redisson.client.codec.Codec; import org.redisson.core.RObject; +import org.redisson.liveobject.annotation.REntity; /** * @@ -40,10 +41,18 @@ public class DefaultCodecProvider implements CodecProvider { return codecCache.get(codecClass); } + @Override + public Codec getCodec(REntity anno, Class cls) { + if (!cls.isAnnotationPresent(anno.annotationType())) { + throw new IllegalArgumentException("Annotation REntity does not present on type [" + cls.getCanonicalName() + "]"); + } + return getCodec(anno.codec()); + } + @Override public Codec getCodec(Class codecClass, Class rObjectClass, String name) { if (rObjectClass.isInterface()) { - throw new IllegalArgumentException("Cannot lookup an interface class of RObject " + rObjectClass.getCanonicalName() + ". Concrete class only."); + throw new IllegalArgumentException("Cannot lookup an interface class of RObject [" + rObjectClass.getCanonicalName() + "]. Concrete class only."); } return getCodec(codecClass); } @@ -58,10 +67,18 @@ public class DefaultCodecProvider implements CodecProvider { codecCache.putIfAbsent(cls, codec); } + @Override + public void registerCodec(REntity anno, Class cls, Codec codec) { + if (!cls.isAnnotationPresent(anno.getClass())) { + throw new IllegalArgumentException("Annotation REntity does not present on type [" + cls.getCanonicalName() + "]"); + } + registerCodec(anno.codec(), codec); + } + @Override public void registerCodec(Class codecClass, Class rObjectClass, String name, Codec codec) { if (rObjectClass.isInterface()) { - throw new IllegalArgumentException("Cannot register an interface class of RObject " + rObjectClass.getCanonicalName() + ". Concrete class only."); + throw new IllegalArgumentException("Cannot register an interface class of RObject [" + rObjectClass.getCanonicalName() + "]. Concrete class only."); } registerCodec(codecClass, codec); } diff --git a/src/main/java/org/redisson/liveobject/annotation/REntity.java b/src/main/java/org/redisson/liveobject/annotation/REntity.java index 1756d7288..bf717eae4 100644 --- a/src/main/java/org/redisson/liveobject/annotation/REntity.java +++ b/src/main/java/org/redisson/liveobject/annotation/REntity.java @@ -15,11 +15,14 @@ */ package org.redisson.liveobject.annotation; +import io.netty.buffer.Unpooled; +import java.io.IOException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.redisson.client.codec.Codec; +import org.redisson.client.handler.State; import org.redisson.codec.JsonJacksonCodec; /** @@ -46,13 +49,33 @@ public @interface REntity { } - public class DefaultNamingScheme implements NamingScheme { + public abstract class AbstractNamingScheme implements NamingScheme { - public static final DefaultNamingScheme INSTANCE = new DefaultNamingScheme(); + protected final Codec codec; + + public AbstractNamingScheme(Codec codec) { + this.codec = codec; + } + + } + + public class DefaultNamingScheme extends AbstractNamingScheme implements NamingScheme { + + public static final DefaultNamingScheme INSTANCE = new DefaultNamingScheme(new JsonJacksonCodec()); + private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); + + public DefaultNamingScheme(Codec codec) { + super(codec); + } @Override public String getName(Class cls, String idFieldName, Object id) { - return "redisson_live_object:{class=" + cls.getName() + ", " + idFieldName + "=" + id.toString() + "}"; + try { + String encode = bytesToHex(codec.getMapKeyEncoder().encode(id)); + return "redisson_live_object:{class=" + cls.getName() + ", " + idFieldName + "=" + encode + "}"; + } catch (IOException ex) { + throw new IllegalArgumentException("Unable to encode id [" + id + "] into byte[]", ex); + } } @Override @@ -67,8 +90,32 @@ public @interface REntity { @Override public Object resolveId(String name) { - return name.substring(name.indexOf("=", name.indexOf("=") + 1) + 1, name.length() - 1); + String decode = name.substring(name.indexOf("=", name.indexOf("=") + 1) + 1, name.length() - 1); + try { + return codec.getMapKeyDecoder().decode(Unpooled.wrappedBuffer(hexToBytes(decode)), new State(false)); + } catch (IOException ex) { + throw new IllegalStateException("Unable to decode [" + decode + "] into object", ex); + } } + public static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + public static byte[] hexToBytes(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } } } diff --git a/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java b/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java index cb89d7563..661649b36 100644 --- a/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java +++ b/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java @@ -53,7 +53,7 @@ public class AccessorInterceptor { @RuntimeType public Object intercept(@Origin Method method, @SuperCall Callable superMethod, - @AllArguments Object[] args, @This Object me, + @AllArguments Object[] args, @This Object me, @FieldValue("liveObjectLiveMap") RMap liveMap) throws Exception { if (isGetter(method, getREntityIdFieldName(me))) { return ((RLiveObject) me).getLiveObjectId(); @@ -73,7 +73,10 @@ public class AccessorInterceptor { if (isSetter(method, fieldName)) { if (args[0].getClass().getSuperclass().isAnnotationPresent(REntity.class)) { Class rEntity = args[0].getClass().getSuperclass(); - REntity.NamingScheme ns = rEntity.getAnnotation(REntity.class).namingScheme().newInstance(); + REntity anno = rEntity.getAnnotation(REntity.class); + REntity.NamingScheme ns = anno.namingScheme() + .getDeclaredConstructor(Codec.class) + .newInstance(codecProvider.getCodec(anno, (Class) rEntity)); return liveMap.put(fieldName, new RedissonReference(rEntity, ns.getName(rEntity, getREntityIdFieldName(args[0]), ((RLiveObject) args[0]).getLiveObjectId()))); } else if (args[0] instanceof RObject) { RObject ar = (RObject) args[0]; @@ -115,7 +118,10 @@ public class AccessorInterceptor { Class type = rr.getType(); if (type != null) { if (type.isAnnotationPresent(REntity.class)) { - REntity.NamingScheme ns = type.getAnnotation(REntity.class).namingScheme().newInstance(); + 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()) { diff --git a/src/main/java/org/redisson/liveobject/core/LiveObjectInterceptor.java b/src/main/java/org/redisson/liveobject/core/LiveObjectInterceptor.java index bc2c5688b..7935cd824 100644 --- a/src/main/java/org/redisson/liveobject/core/LiveObjectInterceptor.java +++ b/src/main/java/org/redisson/liveobject/core/LiveObjectInterceptor.java @@ -58,9 +58,9 @@ public class LiveObjectInterceptor { this.codecProvider = codecProvider; this.originalClass = entityClass; this.idFieldName = idFieldName; - REntity anno = ((REntity) entityClass.getAnnotation(REntity.class)); - this.namingScheme = anno.namingScheme().newInstance(); + REntity anno = (REntity) entityClass.getAnnotation(REntity.class); this.codecClass = anno.codec(); + this.namingScheme = anno.namingScheme().getDeclaredConstructor(Codec.class).newInstance(codecProvider.getCodec(anno, originalClass)); } @RuntimeType @@ -76,6 +76,9 @@ public class LiveObjectInterceptor { @FieldProxy("liveObjectLiveMap") Getter mapGetter ) throws Exception { if ("setLiveObjectId".equals(method.getName())) { + if (args[0].getClass().isArray()) { + throw new UnsupportedOperationException("RId value cannot be an array."); + } //TODO: distributed locking maybe required. String idKey = getMapKey(args[0]); if (map != null) { diff --git a/src/test/java/org/redisson/RedissonAttachedLiveObjectServiceTest.java b/src/test/java/org/redisson/RedissonAttachedLiveObjectServiceTest.java index 592903f9e..359c58f8d 100644 --- a/src/test/java/org/redisson/RedissonAttachedLiveObjectServiceTest.java +++ b/src/test/java/org/redisson/RedissonAttachedLiveObjectServiceTest.java @@ -1,8 +1,12 @@ package org.redisson; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; import static org.junit.Assert.*; import org.junit.Test; import org.redisson.core.RMap; @@ -316,4 +320,48 @@ public class RedissonAttachedLiveObjectServiceTest extends BaseTest { s.get(TestREntityValueNested.class, "122") .getValue().getValue().get("field")); } + + @REntity + public static class TestSerializable { + + @RId + private Serializable id; + + private String value; + + public Serializable getId() { + return id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + } + + @Test + public void testSerializerable() { + RLiveObjectService liveObjectService = redisson.getLiveObjectService(); + + TestSerializable t = liveObjectService.get(TestSerializable.class, "55555"); + assertTrue(Objects.equals("55555", t.getId())); + t = liveObjectService.get(TestSerializable.class, 90909l); + assertTrue(Objects.equals(90909l, t.getId())); + + t = liveObjectService.get(TestSerializable.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}); + } catch (Exception e) { + assertEquals("RId value cannot be an array.", e.getCause().getMessage()); + } + } + }