Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
1. Simplified the API
2. Added class validation
3. Nested RObject and LiveObject is now supported
  • Loading branch information
jackygurui committed Jun 4, 2016
1 parent 7db9f30 commit a8945c6
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 62 deletions.
4 changes: 2 additions & 2 deletions src/main/java/org/redisson/Redisson.java
Expand Up @@ -532,8 +532,8 @@ public RBatch createBatch() {
} }


@Override @Override
public <T, K> RedissonAttachedLiveObjectService<T, K> getAttachedLiveObjectService() { public RedissonAttachedLiveObjectService getAttachedLiveObjectService() {
return new RedissonAttachedLiveObjectService<T, K>(this, commandExecutor); return new RedissonAttachedLiveObjectService(this, commandExecutor);
} }


@Override @Override
Expand Down
67 changes: 52 additions & 15 deletions src/main/java/org/redisson/RedissonAttachedLiveObjectService.java
@@ -1,73 +1,110 @@
package org.redisson; package org.redisson;


import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.PlatformDependent;
import java.lang.reflect.Modifier;
import java.util.Map; import java.util.Map;
import net.bytebuddy.ByteBuddy; import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.matcher.ElementMatchers;
import org.redisson.command.CommandExecutor; import org.redisson.command.CommandAsyncExecutor;
import org.redisson.core.RObject;
import org.redisson.liveobject.RAttachedLiveObjectService; import org.redisson.liveobject.RAttachedLiveObjectService;
import org.redisson.liveobject.annotation.REntity;
import org.redisson.liveobject.annotation.RId; import org.redisson.liveobject.annotation.RId;
import org.redisson.liveobject.core.AccessorInterceptor; import org.redisson.liveobject.core.AccessorInterceptor;
import org.redisson.liveobject.misc.Introspectior; import org.redisson.liveobject.misc.Introspectior;


public class RedissonAttachedLiveObjectService<T, K> implements RAttachedLiveObjectService<T, K> { public class RedissonAttachedLiveObjectService implements RAttachedLiveObjectService {


private static final Map<Class, Class> classCache private static final Map<Class, Class> classCache
= PlatformDependent.<Class, Class>newConcurrentHashMap(); = PlatformDependent.<Class, Class>newConcurrentHashMap();
private static final Map<Class, Class> proxyCache private static final Map<Class, Class> proxyCache
= PlatformDependent.<Class, Class>newConcurrentHashMap(); = PlatformDependent.<Class, Class>newConcurrentHashMap();

private final RedissonClient redisson; private final RedissonClient redisson;
private final CommandExecutor commandExecutor; private final CommandAsyncExecutor commandExecutor;


public RedissonAttachedLiveObjectService(RedissonClient redisson, CommandExecutor commandExecutor) { public RedissonAttachedLiveObjectService(RedissonClient redisson, CommandAsyncExecutor commandExecutor) {
this.redisson = redisson; this.redisson = redisson;
this.commandExecutor = commandExecutor; this.commandExecutor = commandExecutor;
} }


//TODO: Support ID Generator //TODO: Support ID Generator

@Override @Override
public T get(Class<T> entityClass, K id, long ttl) { public <T, K> T get(Class<T> entityClass, K id, long ttl) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
} }


@Override @Override
public T get(Class<T> entityClass, K id) { public <T, K> T get(Class<T> entityClass, K id) {
try { try {
//TODO: support class with no arg constructor //TODO: support class with no arg constructor
return getProxyClass(entityClass).getConstructor(id.getClass()).newInstance(id); return getProxyClass(entityClass).getConstructor(id.getClass()).newInstance(id);
} catch (Exception ex) { } catch (Exception ex) {
unregisterClass(entityClass);
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
} }


private Class<? extends T> getProxyClass(Class<T> entityClass) throws Exception { private <T, K> Class<? extends T> getProxyClass(Class<T> entityClass) throws Exception {
if (!classCache.containsKey(entityClass)) { if (!classCache.containsKey(entityClass)) {
registerClass(entityClass); registerClass(entityClass);
} }
return classCache.get(entityClass); return classCache.get(entityClass);
} }


private void registerClass(Class<T> entityClass) throws Exception { private <T, K> void registerClass(Class<T> entityClass) throws Exception {
//TODO: check annotation on the entityClass if (entityClass.isAnonymousClass() || entityClass.isLocalClass()) {
String idFieldName = Introspectior.getFieldsWithAnnotation(entityClass, RId.class) throw new IllegalArgumentException(entityClass.getName() + " is not publically accessable.");
.getOnly() }
.getName(); if (!entityClass.isAnnotationPresent(REntity.class)) {
throw new IllegalArgumentException("REntity annotation is missing from class type declaration.");
}
FieldList<FieldDescription.InDefinedShape> fieldsWithRIdAnnotation = Introspectior.getFieldsWithAnnotation(entityClass, RId.class);
if (fieldsWithRIdAnnotation.size() == 0) {
throw new IllegalArgumentException("RId annotation is missing from class field declaration.");
}
if (fieldsWithRIdAnnotation.size() > 1) {
throw new IllegalArgumentException("Only one field with RId annotation is allowed in class field declaration.");
}
FieldDescription.ForLoadedField idField = (FieldDescription.ForLoadedField) fieldsWithRIdAnnotation.getOnly();
String idFieldName = idField.getName();
if (entityClass.getDeclaredField(idFieldName).getType().isAnnotationPresent(REntity.class)) {
throw new IllegalArgumentException("Field with RId annotation cannot be a type of which class is annotated with REntity.");
}
if (entityClass.getDeclaredField(idFieldName).getType().isAssignableFrom(RObject.class)) {
throw new IllegalArgumentException("Field with RId annotation cannot be a type of RObject");
}
classCache.putIfAbsent(entityClass, new ByteBuddy() classCache.putIfAbsent(entityClass, new ByteBuddy()
.subclass(entityClass) .subclass(entityClass)
.method(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class)) .method(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class))
.and(ElementMatchers.isGetter() .and(ElementMatchers.isGetter()
.or(ElementMatchers.isSetter())) .or(ElementMatchers.isSetter()))
.and(ElementMatchers.isPublic())) .and(ElementMatchers.isPublic()))
.intercept(MethodDelegation.to(new AccessorInterceptor<T, K>(redisson, entityClass, idFieldName, commandExecutor))) .intercept(MethodDelegation.to(new AccessorInterceptor(redisson, entityClass, idFieldName, commandExecutor)))
.make().load(getClass().getClassLoader(), .make().load(getClass().getClassLoader(),
ClassLoadingStrategy.Default.WRAPPER) ClassLoadingStrategy.Default.WRAPPER)
.getLoaded()); .getLoaded());
proxyCache.putIfAbsent(classCache.get(entityClass), entityClass); proxyCache.putIfAbsent(classCache.get(entityClass), entityClass);
} }


public static void unregisterProxy(Class proxy) {
Class cls = proxyCache.remove(proxy);
if (cls != null) {
classCache.remove(cls);
}
}

public static void unregisterClass(Class cls) {
Class proxy = classCache.remove(cls);
if (proxy != null) {
proxyCache.remove(proxy);
}
}

public static Class getActualClass(Class proxyClass) { public static Class getActualClass(Class proxyClass) {
return proxyCache.get(proxyClass); return proxyCache.get(proxyClass);
} }
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/org/redisson/RedissonClient.java
Expand Up @@ -639,7 +639,13 @@ public interface RedissonClient {
*/ */
RKeys getKeys(); RKeys getKeys();


<T, K> RedissonAttachedLiveObjectService<T, K> getAttachedLiveObjectService(); /**
* Returns RedissonAttachedLiveObjectService which can be used to
* retrieve live REntity(s)
*
* @return
*/
RedissonAttachedLiveObjectService getAttachedLiveObjectService();


/** /**
* Shuts down Redisson instance <b>NOT</b> Redis server * Shuts down Redisson instance <b>NOT</b> Redis server
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/redisson/RedissonReference.java
Expand Up @@ -67,7 +67,9 @@ public void setKeyName(Object keyName) {
* @return the codec * @return the codec
*/ */
public Codec getCodec() throws Exception { public Codec getCodec() throws Exception {
return (Codec) Class.forName(codec).newInstance(); return codec == null
? null
: (Codec) Class.forName(codec).newInstance();
} }


/** /**
Expand Down
Expand Up @@ -4,10 +4,8 @@
* *
* @author ruigu * @author ruigu
* *
* @param <T> Entity type
* @param <K> Key type
*/ */
public interface RAttachedLiveObjectService<T, K> extends RLiveObjectService<T, K> { public interface RAttachedLiveObjectService extends RLiveObjectService {


/** /**
* Finds the entity from Redis with the id. * Finds the entity from Redis with the id.
Expand All @@ -18,8 +16,10 @@ public interface RAttachedLiveObjectService<T, K> extends RLiveObjectService<T,
* of this object will renew this. If it is not been accessed * of this object will renew this. If it is not been accessed
* before the ttl reaches. This object is then expires and * before the ttl reaches. This object is then expires and
* removed from redis. Think of it is been garbage collected. * removed from redis. Think of it is been garbage collected.
* @param <T> Entity type
* @param <K> Key type
* @return In ATTACHED Mode, this always returns a proxy class. Even it does * @return In ATTACHED Mode, this always returns a proxy class. Even it does
* not exist in redis. * not exist in redis.
*/ */
public T get(Class<T> entityClass, K id, long ttl); public <T, K> T get(Class<T> entityClass, K id, long ttl);
} }
Expand Up @@ -9,21 +9,7 @@
* @param <T> Entity type * @param <T> Entity type
* @param <K> Key type * @param <K> Key type
*/ */
public interface RDetachedLiveObjectService<T, K> extends RLiveObjectService<T, K> { public interface RDetachedLiveObjectService<T, K> extends RLiveObjectService {


/**
* Finds the entity from Redis with the id.
*
* @param entityClass Entity class
* @param id identifier
* @return In ATTACHED Mode, this always returns a proxy class. Even it does
* not exist in redis.
* In DETACHED Mode, this returns an instance of the entity class.
* IF it doesn't exist in redis, null is returned.
*/
@Override
public T get(Class<T> entityClass, K id);


/** /**
* Finds the entity from Redis with the id. * Finds the entity from Redis with the id.
Expand All @@ -34,8 +20,6 @@ public interface RDetachedLiveObjectService<T, K> extends RLiveObjectService<T,
* not exist in redis. * not exist in redis.
* In DETACHED Mode, this returns an instance of the entity class. * In DETACHED Mode, this returns an instance of the entity class.
* IF it doesn't exist in redis, a runtime exception is thrown. * IF it doesn't exist in redis, a runtime exception is thrown.
* @throws org.redisson.liveobject.REntityNotFoundException A Runtime
* exception is thrown when the entity does not exist in Redis.
*/ */
public Future<T> getAsync(Class<T> entityClass, K id); public Future<T> getAsync(Class<T> entityClass, K id);


Expand Down
10 changes: 5 additions & 5 deletions src/main/java/org/redisson/liveobject/RLiveObjectService.java
Expand Up @@ -10,22 +10,22 @@
* Redis when update is called. * Redis when update is called.
* *
* @author ruigu * @author ruigu
* *
* @param <T> Entity type
* @param <K> Key type
*/ */
public interface RLiveObjectService<T, K> { public interface RLiveObjectService {


/** /**
* Finds the entity from Redis with the id. * Finds the entity from Redis with the id.
* *
* @param entityClass Entity class * @param entityClass Entity class
* @param id identifier * @param id identifier
* @param <T> Entity type
* @param <K> Key type
* @return In ATTACHED Mode, this always returns a proxy class. Even it does * @return In ATTACHED Mode, this always returns a proxy class. Even it does
* not exist in redis. In DETACHED Mode, this returns an instance of the * not exist in redis. In DETACHED Mode, this returns an instance of the
* entity class. IF it doesn't exist in redis, a runtime exception is * entity class. IF it doesn't exist in redis, a runtime exception is
* thrown. * thrown.
*/ */
public T get(Class<T> entityClass, K id); public <T, K> T get(Class<T> entityClass, K id);


} }
Expand Up @@ -28,7 +28,7 @@ public class DefaultNamingScheme implements NamingScheme {


@Override @Override
public String getName(Class cls, String idFieldName, Object id) { public String getName(Class cls, String idFieldName, Object id) {
return "redisson_live_object:{class=" + cls.getCanonicalName() + ", " + idFieldName + "=" + id.toString() + "}"; return "redisson_live_object:{class=" + cls.getName() + ", " + idFieldName + "=" + id.toString() + "}";
} }


} }
Expand Down
36 changes: 22 additions & 14 deletions src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java
Expand Up @@ -12,7 +12,7 @@
import org.redisson.RedissonReference; import org.redisson.RedissonReference;
import org.redisson.client.RedisException; import org.redisson.client.RedisException;
import org.redisson.client.codec.Codec; import org.redisson.client.codec.Codec;
import org.redisson.command.CommandExecutor; import org.redisson.command.CommandAsyncExecutor;
import org.redisson.core.RMap; import org.redisson.core.RMap;
import org.redisson.core.RObject; import org.redisson.core.RObject;
import org.redisson.liveobject.annotation.REntity; import org.redisson.liveobject.annotation.REntity;
Expand All @@ -23,33 +23,36 @@
* *
* @author ruigu * @author ruigu
*/ */
public class AccessorInterceptor<T, K> { public class AccessorInterceptor {


private final RedissonClient redisson; private final RedissonClient redisson;
private final Class originalClass; private final Class originalClass;
private final String idFieldName; private final String idFieldName;
private final REntity.NamingScheme namingScheme; private final REntity.NamingScheme namingScheme;
private final CommandExecutor commandExecutor; private final CommandAsyncExecutor commandExecutor;
private RMap liveMap; private RMap liveMap;


public AccessorInterceptor(RedissonClient redisson, Class entityClass, String idFieldName, CommandExecutor commandExecutor) throws Exception { public AccessorInterceptor(RedissonClient redisson, Class entityClass,
String idFieldName, CommandAsyncExecutor commandExecutor) throws Exception {
this.redisson = redisson; this.redisson = redisson;
this.originalClass = entityClass; this.originalClass = entityClass;
this.idFieldName = idFieldName; this.idFieldName = idFieldName;
this.commandExecutor = commandExecutor; this.commandExecutor = commandExecutor;
this.namingScheme = ((REntity) entityClass.getAnnotation(REntity.class)).namingScheme().newInstance(); this.namingScheme = ((REntity) entityClass.getAnnotation(REntity.class))
.namingScheme().newInstance();
} }


@RuntimeType @RuntimeType
public Object intercept(@Origin Method method, @SuperCall Callable<?> superMethod, @AllArguments Object[] args, @This T me) throws Exception { public Object intercept(@Origin Method method, @SuperCall Callable<?> superMethod,
@AllArguments Object[] args, @This Object me) throws Exception {
if (isGetter(method, idFieldName)) { if (isGetter(method, idFieldName)) {
return superMethod.call(); return superMethod.call();
} }
initLiveMapIfRequired(getId(me)); initLiveMapIfRequired(getId(me));
if (isSetter(method, idFieldName)) { if (isSetter(method, idFieldName)) {
superMethod.call(); superMethod.call();
try { try {
liveMap.rename(getMapKey((K) args[0])); liveMap.rename(getMapKey(args[0]));
} catch (RedisException e) { } catch (RedisException e) {
if (e.getMessage() == null || !e.getMessage().startsWith("ERR no such key")) { if (e.getMessage() == null || !e.getMessage().startsWith("ERR no such key")) {
throw e; throw e;
Expand All @@ -62,10 +65,15 @@ public Object intercept(@Origin Method method, @SuperCall Callable<?> superMetho
if (isGetter(method, fieldName)) { if (isGetter(method, fieldName)) {
Object result = liveMap.get(fieldName); Object result = liveMap.get(fieldName);
if (method.getReturnType().isAnnotationPresent(REntity.class)) { if (method.getReturnType().isAnnotationPresent(REntity.class)) {
return redisson.getAttachedLiveObjectService().get((Class<Object>) method.getReturnType(), result); return redisson.getAttachedLiveObjectService()
.get((Class<Object>) method.getReturnType(), result);
} else if (result instanceof RedissonReference) { } else if (result instanceof RedissonReference) {
RedissonReference r = ((RedissonReference) result); RedissonReference r = ((RedissonReference) result);
return r.getType().getConstructor(Codec.class, CommandExecutor.class, String.class).newInstance(r.getCodec(), commandExecutor, r.getKeyName()); return r.getType()
.getConstructor(Codec.class, CommandAsyncExecutor.class, String.class)
.newInstance(r.isDefaultCodec()
? commandExecutor.getConnectionManager().getCodec()
: r.getCodec(), commandExecutor, r.getKeyName());
} }
return result; return result;
} }
Expand All @@ -81,7 +89,7 @@ public Object intercept(@Origin Method method, @SuperCall Callable<?> superMetho
return superMethod.call(); return superMethod.call();
} }


private void initLiveMapIfRequired(K id) { private void initLiveMapIfRequired(Object id) {
if (liveMap == null) { if (liveMap == null) {
liveMap = redisson.getMap(getMapKey(id)); liveMap = redisson.getMap(getMapKey(id));
} }
Expand All @@ -101,18 +109,18 @@ private boolean isSetter(Method method, String fieldName) {
&& method.getName().endsWith(getFieldNameSuffix(fieldName)); && method.getName().endsWith(getFieldNameSuffix(fieldName));
} }


private String getMapKey(K id) { private String getMapKey(Object id) {
return namingScheme.getName(originalClass, idFieldName, id); return namingScheme.getName(originalClass, idFieldName, id);
} }


private K getId(T me) throws Exception { private Object getId(Object me) throws Exception {
return (K) originalClass.getDeclaredMethod("get" + getFieldNameSuffix(idFieldName)).invoke(me); return originalClass.getDeclaredMethod("get" + getFieldNameSuffix(idFieldName)).invoke(me);
} }


private static String getFieldNameSuffix(String fieldName) { private static String getFieldNameSuffix(String fieldName) {
return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
} }

private static Object getFieldValue(Object o, String fieldName) throws Exception { private static Object getFieldValue(Object o, String fieldName) throws Exception {
return RedissonAttachedLiveObjectService.getActualClass(o.getClass()).getDeclaredMethod("get" + getFieldNameSuffix(fieldName)).invoke(o); return RedissonAttachedLiveObjectService.getActualClass(o.getClass()).getDeclaredMethod("get" + getFieldNameSuffix(fieldName)).invoke(o);
} }
Expand Down

0 comments on commit a8945c6

Please sign in to comment.