parent
f8dfdb7154
commit
7db9f30577
@ -0,0 +1,75 @@
|
|||||||
|
package org.redisson;
|
||||||
|
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.bytebuddy.ByteBuddy;
|
||||||
|
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||||
|
import net.bytebuddy.implementation.MethodDelegation;
|
||||||
|
import net.bytebuddy.matcher.ElementMatchers;
|
||||||
|
import org.redisson.command.CommandExecutor;
|
||||||
|
import org.redisson.liveobject.RAttachedLiveObjectService;
|
||||||
|
import org.redisson.liveobject.annotation.RId;
|
||||||
|
import org.redisson.liveobject.core.AccessorInterceptor;
|
||||||
|
import org.redisson.liveobject.misc.Introspectior;
|
||||||
|
|
||||||
|
public class RedissonAttachedLiveObjectService<T, K> implements RAttachedLiveObjectService<T, K> {
|
||||||
|
|
||||||
|
private static final Map<Class, Class> classCache
|
||||||
|
= PlatformDependent.<Class, Class>newConcurrentHashMap();
|
||||||
|
private static final Map<Class, Class> proxyCache
|
||||||
|
= PlatformDependent.<Class, Class>newConcurrentHashMap();
|
||||||
|
private final RedissonClient redisson;
|
||||||
|
private final CommandExecutor commandExecutor;
|
||||||
|
|
||||||
|
public RedissonAttachedLiveObjectService(RedissonClient redisson, CommandExecutor commandExecutor) {
|
||||||
|
this.redisson = redisson;
|
||||||
|
this.commandExecutor = commandExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Support ID Generator
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get(Class<T> entityClass, K id, long ttl) {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get(Class<T> entityClass, K id) {
|
||||||
|
try {
|
||||||
|
//TODO: support class with no arg constructor
|
||||||
|
return getProxyClass(entityClass).getConstructor(id.getClass()).newInstance(id);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<? extends T> getProxyClass(Class<T> entityClass) throws Exception {
|
||||||
|
if (!classCache.containsKey(entityClass)) {
|
||||||
|
registerClass(entityClass);
|
||||||
|
}
|
||||||
|
return classCache.get(entityClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerClass(Class<T> entityClass) throws Exception {
|
||||||
|
//TODO: check annotation on the entityClass
|
||||||
|
String idFieldName = Introspectior.getFieldsWithAnnotation(entityClass, RId.class)
|
||||||
|
.getOnly()
|
||||||
|
.getName();
|
||||||
|
classCache.putIfAbsent(entityClass, new ByteBuddy()
|
||||||
|
.subclass(entityClass)
|
||||||
|
.method(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class))
|
||||||
|
.and(ElementMatchers.isGetter()
|
||||||
|
.or(ElementMatchers.isSetter()))
|
||||||
|
.and(ElementMatchers.isPublic()))
|
||||||
|
.intercept(MethodDelegation.to(new AccessorInterceptor<T, K>(redisson, entityClass, idFieldName, commandExecutor)))
|
||||||
|
.make().load(getClass().getClassLoader(),
|
||||||
|
ClassLoadingStrategy.Default.WRAPPER)
|
||||||
|
.getLoaded());
|
||||||
|
proxyCache.putIfAbsent(classCache.get(entityClass), entityClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class getActualClass(Class proxyClass) {
|
||||||
|
return proxyCache.get(proxyClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package org.redisson;
|
||||||
|
|
||||||
|
import org.redisson.client.codec.Codec;
|
||||||
|
import org.redisson.core.RObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ruigu
|
||||||
|
*/
|
||||||
|
public class RedissonReference {
|
||||||
|
private String type;
|
||||||
|
private Object keyName;
|
||||||
|
private String codec;
|
||||||
|
|
||||||
|
public RedissonReference() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public RedissonReference(Class<? extends RObject> type, Object keyName) {
|
||||||
|
this.type = type.getCanonicalName();
|
||||||
|
this.keyName = keyName;
|
||||||
|
this.codec = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RedissonReference(Class<? extends RObject> type, Object keyName, Codec codec) {
|
||||||
|
this.type = type.getCanonicalName();
|
||||||
|
this.keyName = keyName;
|
||||||
|
this.codec = codec.getClass().getCanonicalName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDefaultCodec() {
|
||||||
|
return codec == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the type
|
||||||
|
*/
|
||||||
|
public Class<? extends RObject> getType() {
|
||||||
|
try {
|
||||||
|
return (Class<? extends RObject>) Class.forName(type);
|
||||||
|
} catch (ClassNotFoundException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param type the type to set
|
||||||
|
*/
|
||||||
|
public void setType(Class<? extends RObject> type) {
|
||||||
|
this.type = type.getCanonicalName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the keyName
|
||||||
|
*/
|
||||||
|
public Object getKeyName() {
|
||||||
|
return keyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param keyName the keyName to set
|
||||||
|
*/
|
||||||
|
public void setKeyName(Object keyName) {
|
||||||
|
this.keyName = keyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the codec
|
||||||
|
*/
|
||||||
|
public Codec getCodec() throws Exception {
|
||||||
|
return (Codec) Class.forName(codec).newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param codec the codec to set
|
||||||
|
*/
|
||||||
|
public void setCodec(Codec codec) {
|
||||||
|
this.codec = codec.getClass().getCanonicalName();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package org.redisson.liveobject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ruigu
|
||||||
|
*
|
||||||
|
* @param <T> Entity type
|
||||||
|
* @param <K> Key type
|
||||||
|
*/
|
||||||
|
public interface RAttachedLiveObjectService<T, K> extends RLiveObjectService<T, K> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the entity from Redis with the id.
|
||||||
|
*
|
||||||
|
* @param entityClass Entity class
|
||||||
|
* @param id identifier
|
||||||
|
* @param ttl sets the time to live on the object. Any calls to the accessor
|
||||||
|
* of this object will renew this. If it is not been accessed
|
||||||
|
* before the ttl reaches. This object is then expires and
|
||||||
|
* removed from redis. Think of it is been garbage collected.
|
||||||
|
* @return In ATTACHED Mode, this always returns a proxy class. Even it does
|
||||||
|
* not exist in redis.
|
||||||
|
*/
|
||||||
|
public T get(Class<T> entityClass, K id, long ttl);
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package org.redisson.liveobject;
|
||||||
|
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ruigu
|
||||||
|
*
|
||||||
|
* @param <T> Entity type
|
||||||
|
* @param <K> Key type
|
||||||
|
*/
|
||||||
|
public interface RDetachedLiveObjectService<T, K> extends RLiveObjectService<T, K> {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @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, 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the instance into redis
|
||||||
|
*
|
||||||
|
* @param instance the instance to be persisted
|
||||||
|
* @return K The id of the object.
|
||||||
|
*/
|
||||||
|
public K persist(T instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the instance into redis
|
||||||
|
*
|
||||||
|
* @param instance the instance to be persisted
|
||||||
|
* @return K The id of the object.
|
||||||
|
*/
|
||||||
|
public Future<K> persistAsync(T instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the instance into redis with specified time to live.
|
||||||
|
*
|
||||||
|
* @param instance the instance to be persisted
|
||||||
|
* @param ttl the time to live of the instance
|
||||||
|
* @return K The id of the object.
|
||||||
|
*/
|
||||||
|
public K persist(T instance, long ttl);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the instance into redis with specified time to live.
|
||||||
|
*
|
||||||
|
* @param instance
|
||||||
|
* @param ttl the time to live of the instance
|
||||||
|
* @return K The id of the object.
|
||||||
|
*/
|
||||||
|
public Future<K> persistAsync(T instance, long ttl);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the instance from redis by specifying the id
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
public void remove(K id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the instance from redis by specifying the id
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @return Future.
|
||||||
|
*/
|
||||||
|
public Future<Void> removeAsync(K id);
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package org.redisson.liveobject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pre-registration of each entity class is not necessary.
|
||||||
|
*
|
||||||
|
* In ATTACHED Mode, entity's getters and setters propagate operations to Redis
|
||||||
|
* automatically.
|
||||||
|
*
|
||||||
|
* In DETACHED Mode, entity's field values are kept local and only pushed to
|
||||||
|
* Redis when update is called.
|
||||||
|
*
|
||||||
|
* @author ruigu
|
||||||
|
*
|
||||||
|
* @param <T> Entity type
|
||||||
|
* @param <K> Key type
|
||||||
|
*/
|
||||||
|
public interface RLiveObjectService<T, K> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, a runtime exception is
|
||||||
|
* thrown.
|
||||||
|
*/
|
||||||
|
public T get(Class<T> entityClass, K id);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package org.redisson.liveobject.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ruigu
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.TYPE})
|
||||||
|
public @interface REntity {
|
||||||
|
|
||||||
|
|
||||||
|
Class<? extends NamingScheme> namingScheme() default DefaultNamingScheme.class;
|
||||||
|
|
||||||
|
public interface NamingScheme {
|
||||||
|
|
||||||
|
public String getName(Class cls, String idFieldName, Object id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DefaultNamingScheme implements NamingScheme {
|
||||||
|
|
||||||
|
public static final DefaultNamingScheme INSTANCE = new DefaultNamingScheme();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName(Class cls, String idFieldName, Object id) {
|
||||||
|
return "redisson_live_object:{class=" + cls.getCanonicalName() + ", " + idFieldName + "=" + id.toString() + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package org.redisson.liveobject.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import org.redisson.liveobject.resolver.FieldValueAsIdGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ruigu
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.FIELD})
|
||||||
|
public @interface RId {
|
||||||
|
Class generator() default FieldValueAsIdGenerator.class;
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
package org.redisson.liveobject.core;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import net.bytebuddy.implementation.bind.annotation.AllArguments;
|
||||||
|
import net.bytebuddy.implementation.bind.annotation.Origin;
|
||||||
|
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
||||||
|
import net.bytebuddy.implementation.bind.annotation.SuperCall;
|
||||||
|
import net.bytebuddy.implementation.bind.annotation.This;
|
||||||
|
import org.redisson.RedissonAttachedLiveObjectService;
|
||||||
|
import org.redisson.RedissonClient;
|
||||||
|
import org.redisson.RedissonReference;
|
||||||
|
import org.redisson.client.RedisException;
|
||||||
|
import org.redisson.client.codec.Codec;
|
||||||
|
import org.redisson.command.CommandExecutor;
|
||||||
|
import org.redisson.core.RMap;
|
||||||
|
import org.redisson.core.RObject;
|
||||||
|
import org.redisson.liveobject.annotation.REntity;
|
||||||
|
import org.redisson.liveobject.annotation.RId;
|
||||||
|
import org.redisson.liveobject.misc.Introspectior;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ruigu
|
||||||
|
*/
|
||||||
|
public class AccessorInterceptor<T, K> {
|
||||||
|
|
||||||
|
private final RedissonClient redisson;
|
||||||
|
private final Class originalClass;
|
||||||
|
private final String idFieldName;
|
||||||
|
private final REntity.NamingScheme namingScheme;
|
||||||
|
private final CommandExecutor commandExecutor;
|
||||||
|
private RMap liveMap;
|
||||||
|
|
||||||
|
public AccessorInterceptor(RedissonClient redisson, Class entityClass, String idFieldName, CommandExecutor commandExecutor) throws Exception {
|
||||||
|
this.redisson = redisson;
|
||||||
|
this.originalClass = entityClass;
|
||||||
|
this.idFieldName = idFieldName;
|
||||||
|
this.commandExecutor = commandExecutor;
|
||||||
|
this.namingScheme = ((REntity) entityClass.getAnnotation(REntity.class)).namingScheme().newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RuntimeType
|
||||||
|
public Object intercept(@Origin Method method, @SuperCall Callable<?> superMethod, @AllArguments Object[] args, @This T me) throws Exception {
|
||||||
|
if (isGetter(method, idFieldName)) {
|
||||||
|
return superMethod.call();
|
||||||
|
}
|
||||||
|
initLiveMapIfRequired(getId(me));
|
||||||
|
if (isSetter(method, idFieldName)) {
|
||||||
|
superMethod.call();
|
||||||
|
try {
|
||||||
|
liveMap.rename(getMapKey((K) args[0]));
|
||||||
|
} catch (RedisException e) {
|
||||||
|
if (e.getMessage() == null || !e.getMessage().startsWith("ERR no such key")) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
liveMap = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String fieldName = getFieldName(method);
|
||||||
|
if (isGetter(method, fieldName)) {
|
||||||
|
Object result = liveMap.get(fieldName);
|
||||||
|
if (method.getReturnType().isAnnotationPresent(REntity.class)) {
|
||||||
|
return redisson.getAttachedLiveObjectService().get((Class<Object>) method.getReturnType(), result);
|
||||||
|
} else if (result instanceof RedissonReference) {
|
||||||
|
RedissonReference r = ((RedissonReference) result);
|
||||||
|
return r.getType().getConstructor(Codec.class, CommandExecutor.class, String.class).newInstance(r.getCodec(), commandExecutor, r.getKeyName());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (isSetter(method, fieldName)) {
|
||||||
|
if (method.getParameterTypes()[0].isAnnotationPresent(REntity.class)) {
|
||||||
|
return liveMap.fastPut(fieldName, getREntityId(args[0]));
|
||||||
|
} else if (args[0] instanceof RObject) {
|
||||||
|
RObject ar = (RObject) args[0];
|
||||||
|
return liveMap.fastPut(fieldName, new RedissonReference((Class<RObject>) args[0].getClass(), ar.getName()));
|
||||||
|
}
|
||||||
|
return liveMap.fastPut(fieldName, args[0]);
|
||||||
|
}
|
||||||
|
return superMethod.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initLiveMapIfRequired(K id) {
|
||||||
|
if (liveMap == null) {
|
||||||
|
liveMap = redisson.getMap(getMapKey(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFieldName(Method method) {
|
||||||
|
return method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isGetter(Method method, String fieldName) {
|
||||||
|
return method.getName().startsWith("get")
|
||||||
|
&& method.getName().endsWith(getFieldNameSuffix(fieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSetter(Method method, String fieldName) {
|
||||||
|
return method.getName().startsWith("set")
|
||||||
|
&& method.getName().endsWith(getFieldNameSuffix(fieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMapKey(K id) {
|
||||||
|
return namingScheme.getName(originalClass, idFieldName, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private K getId(T me) throws Exception {
|
||||||
|
return (K) originalClass.getDeclaredMethod("get" + getFieldNameSuffix(idFieldName)).invoke(me);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getFieldNameSuffix(String fieldName) {
|
||||||
|
return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object getFieldValue(Object o, String fieldName) throws Exception {
|
||||||
|
return RedissonAttachedLiveObjectService.getActualClass(o.getClass()).getDeclaredMethod("get" + getFieldNameSuffix(fieldName)).invoke(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object getREntityId(Object o) throws Exception {
|
||||||
|
String idName = Introspectior
|
||||||
|
.getFieldsWithAnnotation(RedissonAttachedLiveObjectService.getActualClass(o.getClass()), RId.class)
|
||||||
|
.getOnly()
|
||||||
|
.getName();
|
||||||
|
return getFieldValue(o, idName);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package org.redisson.liveobject.misc;
|
||||||
|
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import net.bytebuddy.description.field.FieldDescription;
|
||||||
|
import net.bytebuddy.description.field.FieldList;
|
||||||
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import net.bytebuddy.matcher.ElementMatchers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ruigu
|
||||||
|
*/
|
||||||
|
public class Introspectior {
|
||||||
|
|
||||||
|
private static final ConcurrentMap<Class, TypeDescription.ForLoadedType> tCache = PlatformDependent.newConcurrentHashMap();
|
||||||
|
|
||||||
|
public static TypeDescription.ForLoadedType getTypeDescription(Class c) {
|
||||||
|
if (!tCache.containsKey(c)) {
|
||||||
|
return new TypeDescription.ForLoadedType(c);
|
||||||
|
}
|
||||||
|
return tCache.get(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodDescription getMethodDescription(Class c, String method) {
|
||||||
|
if (method == null || method.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getTypeDescription(c)
|
||||||
|
.getDeclaredMethods()
|
||||||
|
.filter(ElementMatchers.hasMethodName(method))
|
||||||
|
.getOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FieldDescription getFieldDescription(Class c, String field) {
|
||||||
|
if (field == null || field.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getTypeDescription(c)
|
||||||
|
.getDeclaredFields()
|
||||||
|
.filter(ElementMatchers.named(field))
|
||||||
|
.getOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FieldList<FieldDescription.InDefinedShape> getFieldsWithAnnotation(Class c, Class<? extends Annotation> a) {
|
||||||
|
return getTypeDescription(c)
|
||||||
|
.getDeclaredFields()
|
||||||
|
.filter(ElementMatchers.isAnnotatedWith(a));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package org.redisson.liveobject.resolver;
|
||||||
|
|
||||||
|
import org.redisson.liveobject.annotation.RId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ruigu
|
||||||
|
*/
|
||||||
|
public class FieldValueAsIdGenerator implements Resolver<Object, RId, String>{
|
||||||
|
|
||||||
|
public static final FieldValueAsIdGenerator INSTANCE = new FieldValueAsIdGenerator();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolve(Object value, RId index) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package org.redisson.liveobject.resolver;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ruigu
|
||||||
|
* @param <T> Field instance
|
||||||
|
* @param <A> Annotation to resolve
|
||||||
|
*/
|
||||||
|
public interface Resolver<T, A extends Annotation, V> {
|
||||||
|
|
||||||
|
public V resolve(T value, A index);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package org.redisson;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.redisson.core.RMap;
|
||||||
|
import org.redisson.liveobject.annotation.REntity;
|
||||||
|
import org.redisson.liveobject.annotation.RId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ruigu
|
||||||
|
*/
|
||||||
|
public class RedissonAttachedLiveObjectServiceTest extends BaseTest {
|
||||||
|
|
||||||
|
@REntity
|
||||||
|
public static class TestREntity implements Comparable<TestREntity>, Serializable {
|
||||||
|
|
||||||
|
@RId
|
||||||
|
private String name;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public TestREntity(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestREntity(String name, String value) {
|
||||||
|
super();
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(TestREntity o) {
|
||||||
|
int res = name.compareTo(o.name);
|
||||||
|
if (res == 0) {
|
||||||
|
return value.compareTo(o.value);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
RedissonAttachedLiveObjectService<TestREntity, String> s = redisson.<TestREntity, String>getAttachedLiveObjectService();
|
||||||
|
TestREntity t = s.get(TestREntity.class, "1");
|
||||||
|
assertEquals("1", t.getName());
|
||||||
|
assertTrue(!redisson.getMap(REntity.DefaultNamingScheme.INSTANCE.getName(TestREntity.class, "name", "1")).isExists());
|
||||||
|
t.setName("3333");
|
||||||
|
assertEquals("3333", t.getName());
|
||||||
|
assertTrue(!redisson.getMap(REntity.DefaultNamingScheme.INSTANCE.getName(TestREntity.class, "name", "3333")).isExists());
|
||||||
|
t.setValue("111");
|
||||||
|
assertEquals("111", t.getValue());
|
||||||
|
assertTrue(redisson.getMap(REntity.DefaultNamingScheme.INSTANCE.getName(TestREntity.class, "name", "3333")).isExists());
|
||||||
|
assertTrue(!redisson.getMap(REntity.DefaultNamingScheme.INSTANCE.getName(TestREntity.class, "name", "1")).isExists());
|
||||||
|
assertEquals("111", redisson.getMap(REntity.DefaultNamingScheme.INSTANCE.getName(TestREntity.class, "name", "3333")).get("value"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue