Merge pull request #527 from jackygurui/live-object

Live object
pull/566/head
Nikita Koksharov 9 years ago committed by GitHub
commit fdaaf99cfe

@ -26,6 +26,7 @@ env:
- REDIS_VERSION=3.2.0 REDISSON_TEST=org.redisson.RedissonAtomicDoubleTest
- REDIS_VERSION=3.2.0 REDISSON_TEST=org.redisson.RedissonAtomicLongReactiveTest
- REDIS_VERSION=3.2.0 REDISSON_TEST=org.redisson.RedissonAtomicLongTest
- REDIS_VERSION=3.2.0 REDISSON_TEST=org.redisson.RedissonLiveObjectServiceTest
- REDIS_VERSION=3.2.0 REDISSON_TEST=org.redisson.RedissonBatchTest
- REDIS_VERSION=3.2.0 REDISSON_TEST=org.redisson.RedissonBitSetReactiveTest
- REDIS_VERSION=3.2.0 REDISSON_TEST=org.redisson.RedissonBitSetTest
@ -88,6 +89,7 @@ env:
- REDIS_VERSION=3.0.7 REDISSON_TEST=org.redisson.RedissonAtomicDoubleTest
- REDIS_VERSION=3.0.7 REDISSON_TEST=org.redisson.RedissonAtomicLongReactiveTest
- REDIS_VERSION=3.0.7 REDISSON_TEST=org.redisson.RedissonAtomicLongTest
- REDIS_VERSION=3.0.7 REDISSON_TEST=org.redisson.RedissonLiveObjectServiceTest
- REDIS_VERSION=3.0.7 REDISSON_TEST=org.redisson.RedissonBatchTest
- REDIS_VERSION=3.0.7 REDISSON_TEST=org.redisson.RedissonBitSetReactiveTest
- REDIS_VERSION=3.0.7 REDISSON_TEST=org.redisson.RedissonBitSetTest
@ -149,6 +151,7 @@ env:
- REDIS_VERSION=2.8.24 REDISSON_TEST=org.redisson.RedissonAtomicDoubleTest
- REDIS_VERSION=2.8.24 REDISSON_TEST=org.redisson.RedissonAtomicLongReactiveTest
- REDIS_VERSION=2.8.24 REDISSON_TEST=org.redisson.RedissonAtomicLongTest
- REDIS_VERSION=2.8.24 REDISSON_TEST=org.redisson.RedissonLiveObjectServiceTest
- REDIS_VERSION=2.8.24 REDISSON_TEST=org.redisson.RedissonBatchTest
- REDIS_VERSION=2.8.24 REDISSON_TEST=org.redisson.RedissonBitSetReactiveTest
- REDIS_VERSION=2.8.24 REDISSON_TEST=org.redisson.RedissonBitSetTest

@ -219,7 +219,16 @@
<artifactId>zero-allocation-hashing</artifactId>
<version>0.5</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.3.19</version>
</dependency>
<dependency>
<groupId>org.jodd</groupId>
<artifactId>jodd-bean</artifactId>
<version>3.7.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>

@ -74,8 +74,14 @@ import org.redisson.core.RSetMultimap;
import org.redisson.core.RSetMultimapCache;
import org.redisson.core.RSortedSet;
import org.redisson.core.RTopic;
import org.redisson.liveobject.provider.CodecProvider;
import org.redisson.liveobject.provider.DefaultCodecProvider;
import org.redisson.api.RLiveObjectService;
import io.netty.util.concurrent.Future;
import io.netty.util.internal.PlatformDependent;
import org.redisson.liveobject.provider.DefaultResolverProvider;
import org.redisson.liveobject.provider.ResolverProvider;
/**
* Main infrastructure class allows to get access
@ -89,6 +95,11 @@ public class Redisson implements RedissonClient {
private final EvictionScheduler evictionScheduler;
private final CommandExecutor commandExecutor;
private final ConnectionManager connectionManager;
private final Map<Class, Class> liveObjectClassCache
= PlatformDependent.<Class, Class>newConcurrentHashMap();
private final CodecProvider liveObjectDefaultCodecProvider = new DefaultCodecProvider();
private final ResolverProvider liveObjectDefaultResolverProvider = new DefaultResolverProvider();
private final Config config;
private final UUID id = UUID.randomUUID();
@ -392,6 +403,7 @@ public class Redisson implements RedissonClient {
return new RedissonScript(commandExecutor);
}
@Override
public RRemoteService getRemoteSerivce() {
return new RedissonRemoteService(this);
}
@ -541,6 +553,16 @@ public class Redisson implements RedissonClient {
return new RedissonBatch(evictionScheduler, connectionManager);
}
@Override
public RLiveObjectService getLiveObjectService() {
return new RedissonLiveObjectService(this, liveObjectClassCache, liveObjectDefaultCodecProvider, liveObjectDefaultResolverProvider);
}
@Override
public RLiveObjectService getLiveObjectService(CodecProvider codecProvider, ResolverProvider resolverProvider) {
return new RedissonLiveObjectService(this, liveObjectClassCache, codecProvider, resolverProvider);
}
@Override
public void shutdown() {
connectionManager.shutdown();

@ -21,6 +21,7 @@ import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandExecutor;
import org.redisson.core.ClusterNode;
import org.redisson.core.ClusterNodesGroup;
import org.redisson.core.Node;
@ -59,6 +60,9 @@ import org.redisson.core.RSetMultimap;
import org.redisson.core.RSetMultimapCache;
import org.redisson.core.RSortedSet;
import org.redisson.core.RTopic;
import org.redisson.liveobject.provider.CodecProvider;
import org.redisson.api.RLiveObjectService;
import org.redisson.liveobject.provider.ResolverProvider;
/**
* Main Redisson interface for access
@ -656,6 +660,23 @@ public interface RedissonClient {
*/
RKeys getKeys();
/**
* Returns RedissonAttachedLiveObjectService which can be used to
* retrieve live REntity(s)
*
* @return
*/
RLiveObjectService getLiveObjectService();
/**
* Returns RedissonAttachedLiveObjectService which can be used to
* retrieve live REntity(s)
*
* @param codecProvider the CodecProvider to be used to create the service
* @return
*/
RLiveObjectService getLiveObjectService(CodecProvider codecProvider, ResolverProvider resolverProvider);
/**
* Shuts down Redisson instance <b>NOT</b> Redis server
*

@ -0,0 +1,334 @@
/**
* 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 java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;
import jodd.bean.BeanCopy;
import jodd.bean.BeanUtil;
//import java.util.concurrent.TimeUnit;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.dynamic.DynamicType;
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.core.RExpirable;
//import org.redisson.core.RExpirableAsync;
//import org.redisson.core.RMap;
import org.redisson.core.RObject;
//import org.redisson.core.RObjectAsync;
import org.redisson.liveobject.provider.CodecProvider;
import org.redisson.liveobject.LiveObjectTemplate;
import org.redisson.api.RLiveObjectService;
import org.redisson.api.RLiveObject;
import org.redisson.liveobject.provider.ResolverProvider;
import org.redisson.liveobject.annotation.REntity;
import org.redisson.liveobject.annotation.RId;
import org.redisson.liveobject.core.AccessorInterceptor;
//import org.redisson.liveobject.core.ExpirableInterceptor;
import org.redisson.liveobject.core.LiveObjectInterceptor;
import org.redisson.liveobject.misc.Introspectior;
import org.redisson.liveobject.resolver.Resolver;
public class RedissonLiveObjectService implements RLiveObjectService {
private final Map<Class, Class> classCache;
private final RedissonClient redisson;
private final CodecProvider codecProvider;
private final ResolverProvider resolverProvider;
public RedissonLiveObjectService(RedissonClient redisson, Map<Class, Class> classCache, CodecProvider codecProvider, ResolverProvider resolverProvider) {
this.redisson = redisson;
this.classCache = classCache;
this.codecProvider = codecProvider;
this.resolverProvider = resolverProvider;
}
//TODO: Add ttl renewal functionality
// @Override
// public <T, K> T get(Class<T> entityClass, K id, long timeToLive, TimeUnit timeUnit) {
// T instance = get(entityClass, id);
// RMap map = ((RLiveObject) instance).getLiveObjectLiveMap();
// map.put("RLiveObjectDefaultTimeToLiveValue", timeToLive);
// map.put("RLiveObjectDefaultTimeToLiveUnit", timeUnit.toString());
// map.expire(timeToLive, timeUnit);
// return instance;
// }
@Override
public <T> T create(Class<T> entityClass) {
try {
Class<? extends T> proxyClass = getProxyClass(entityClass);
String idFieldName = getRIdFieldName(entityClass);
RId annotation = entityClass
.getDeclaredField(idFieldName)
.getAnnotation(RId.class);
Resolver resolver = resolverProvider.getResolver(entityClass,
annotation.generator(), annotation);
Object id = resolver.resolve(entityClass, annotation, idFieldName, redisson);
T proxied = instantiateLiveObject(proxyClass, id);
return asLiveObject(proxied).isExists() ? null : proxied;
} catch (Exception ex) {
unregisterClass(entityClass);
throw new RuntimeException(ex);
}
}
@Override
public <T, K> T get(Class<T> entityClass, K id) {
try {
T proxied = instantiateLiveObject(getProxyClass(entityClass), id);
return asLiveObject(proxied).isExists() ? proxied : null;
} catch (Exception ex) {
unregisterClass(entityClass);
throw new RuntimeException(ex);
}
}
@Override
public <T, K> T getOrCreate(Class<T> entityClass, K id) {
try {
return instantiateLiveObject(getProxyClass(entityClass), id);
} catch (Exception ex) {
unregisterClass(entityClass);
throw new RuntimeException(ex);
}
}
@Override
public <T> T attach(T detachedObject) {
validateDetached(detachedObject);
Class<T> entityClass = (Class<T>) detachedObject.getClass();
try {
Class<? extends T> proxyClass = getProxyClass(entityClass);
return instantiateLiveObject(proxyClass,
BeanUtil.pojo.getSimpleProperty(detachedObject,
getRIdFieldName(detachedObject.getClass())));
} catch (Exception ex) {
unregisterClass(entityClass);
throw new RuntimeException(ex);
}
}
@Override
public <T> T merge(T detachedObject) {
T attachedObject = attach(detachedObject);
copy(detachedObject, attachedObject);
return attachedObject;
}
@Override
public <T> T persist(T detachedObject) {
T attachedObject = attach(detachedObject);
if (!asLiveObject(attachedObject).isExists()) {
copy(detachedObject, attachedObject);
return attachedObject;
}
throw new IllegalStateException("This REntity already exists.");
}
@Override
public <T> T detach(T attachedObject) {
validateAttached(attachedObject);
try {
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 RuntimeException(ex);
}
}
@Override
public <T> void delete(T attachedObject) {
validateAttached(attachedObject);
asLiveObject(attachedObject).delete();
}
@Override
public <T, K> void delete(Class<T> entityClass, K id) {
asLiveObject(get(entityClass, id)).delete();
}
@Override
public <T> RLiveObject asLiveObject(T instance) {
return (RLiveObject) instance;
}
@Override
public <T> boolean isLiveObject(T instance) {
return instance instanceof RLiveObject;
}
@Override
public <T> boolean isExists(T instance) {
return instance instanceof RLiveObject && asLiveObject(instance).isExists();
}
@Override
public void registerClass(Class cls) {
if (!classCache.containsKey(cls)) {
validateClass(cls);
registerClassInternal(cls);
}
}
@Override
public void unregisterClass(Class cls) {
classCache.remove(cls.isAssignableFrom(RLiveObject.class) ? cls.getSuperclass() : cls);
}
@Override
public boolean isClassRegistered(Class cls) {
return classCache.containsKey(cls) || classCache.containsValue(cls);
}
private <T> void copy(T detachedObject, T attachedObject) {
String idFieldName = getRIdFieldName(detachedObject.getClass());
BeanCopy.beans(detachedObject, attachedObject)
.ignoreNulls(true)
.exclude(idFieldName)
.copy();
}
private String getRIdFieldName(Class cls) {
return Introspectior.getFieldsWithAnnotation(cls, RId.class)
.getOnly()
.getName();
}
private <T, K> T instantiateLiveObject(Class<T> proxyClass, K id) throws Exception {
T instance = instantiate(proxyClass, id);
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 {
try {
return cls.newInstance();
} catch (Exception exception) {
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);
}
}
}
throw new NoSuchMethodException("Unable to find constructor matching only the RId field type [" + id.getClass().getCanonicalName() + "].");
}
private <T> Class<? extends T> getProxyClass(Class<T> entityClass) {
registerClass(entityClass);
return classCache.get(entityClass);
}
private <T> void validateClass(Class<T> entityClass) {
if (entityClass.isAnonymousClass() || entityClass.isLocalClass()) {
throw new IllegalArgumentException(entityClass.getName() + " is not publically accessable.");
}
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.InDefinedShape idFieldDescription = fieldsWithRIdAnnotation.getOnly();
String idFieldName = idFieldDescription.getName();
Field idField = null;
try {
idField = entityClass.getDeclaredField(idFieldName);
} catch (Exception e) {
throw new IllegalStateException(e);
}
if (idField.getType().isAnnotationPresent(REntity.class)) {
throw new IllegalArgumentException("Field with RId annotation cannot be a type of which class is annotated with REntity.");
}
if (idField.getType().isAssignableFrom(RObject.class)) {
throw new IllegalArgumentException("Field with RId annotation cannot be a type of RObject");
}
}
private <T> void validateDetached(T detachedObject) {
if (detachedObject instanceof RLiveObject) {
throw new IllegalArgumentException("The object supplied is already a RLiveObject");
}
}
private <T> void validateAttached(T attachedObject) {
if (!(attachedObject instanceof RLiveObject)) {
throw new IllegalArgumentException("The object supplied is must be a RLiveObject");
}
}
private <T> void registerClassInternal(Class<T> entityClass) {
DynamicType.Builder<T> builder = new ByteBuddy()
.subclass(entityClass);
for (FieldDescription.InDefinedShape field
: Introspectior.getTypeDescription(LiveObjectTemplate.class)
.getDeclaredFields()) {
builder = builder.define(field);
}
Class<? extends T> proxied = builder.method(ElementMatchers.isDeclaredBy(
Introspectior.getTypeDescription(RLiveObject.class))
.and(ElementMatchers.isGetter().or(ElementMatchers.isSetter())
.or(ElementMatchers.named("isPhantom"))
.or(ElementMatchers.named("delete"))))
.intercept(MethodDelegation.to(
new LiveObjectInterceptor(redisson, codecProvider, entityClass,
getRIdFieldName(entityClass)))
.appendParameterBinder(FieldProxy.Binder
.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.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.isGetter()
.or(ElementMatchers.isSetter()))
.and(ElementMatchers.isPublic()))
.intercept(MethodDelegation.to(
new AccessorInterceptor(redisson, codecProvider, resolverProvider)))
.make().load(getClass().getClassLoader(),
ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
classCache.putIfAbsent(entityClass, proxied);
}
}

@ -128,4 +128,9 @@ abstract class RedissonObject implements RObject {
return commandExecutor.readAsync(getName(), codec, RedisCommands.EXISTS, getName());
}
@Override
public Codec getCodec() {
return codec;
}
}

@ -0,0 +1,121 @@
/**
* 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;
import org.redisson.core.RObject;
import org.redisson.liveobject.annotation.REntity;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class RedissonReference {
private String type;
private String keyName;
private String codec;
public RedissonReference() {
}
public RedissonReference(Class type, String keyName) {
this(type, keyName, null);
}
public RedissonReference(Class type, String keyName, Codec codec) {
if (!type.isAnnotationPresent(REntity.class) && !RObject.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Class reference has to be a type of either RObject or RLiveObject");
}
this.type = type.getName();
this.keyName = keyName;
this.codec = codec != null ? codec.getClass().getCanonicalName() : null;
}
public boolean isDefaultCodec() {
return codec == null;
}
/**
* @return the type
* @throws java.lang.Exception - which could be:
* LinkageError - if the linkage fails
* ExceptionInInitializerError - if the initialization provoked by this method fails
* ClassNotFoundException - if the class cannot be located
*/
public Class<?> getType() throws Exception {
return Class.forName(type);
}
/**
* @return type name in string
*/
public String getTypeName() {
return type;
}
/**
* @param type the type to set
*/
public void setType(Class<?> type) {
if (!type.isAnnotationPresent(REntity.class) && !RObject.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Class reference has to be a type of either RObject or RLiveObject");
}
this.type = type.getCanonicalName();
}
/**
* @return the keyName
*/
public String getKeyName() {
return keyName;
}
/**
* @param keyName the keyName to set
*/
public void setKeyName(String keyName) {
this.keyName = keyName;
}
/**
* @return the codec
* @throws java.lang.Exception - which could be:
* LinkageError - if the linkage fails
* ExceptionInInitializerError - if the initialization provoked by this method fails
* ClassNotFoundException - if the class cannot be located
*/
public Class<? extends Codec> getCodecType() throws Exception {
return (Class<? extends Codec>) (codec == null
? null
: Class.forName(codec));
}
/**
* @return Codec name in string
*/
public String getCodecName() {
return codec;
}
/**
* @param codec the codec to set
*/
public void setCodecType(Class<? extends Codec> codec) {
this.codec = codec.getCanonicalName();
}
}

@ -0,0 +1,66 @@
/**
* 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.api;
//import org.redisson.core.RMap;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public interface RLiveObject {
//
// /**
// * @return the liveObjectLiveMap
// */
// public RMap getLiveObjectLiveMap();
/**
* Returns the value of the field that has the RId annotation.
* @return liveObjectId
*/
Object getLiveObjectId();
/**
* Change the value of the field that has the RId annotation. Since the
* liveObjectId is encoded as a part of the name of the underlying RMap,
* this action will result in renaming the underlying RMap based on the
* naming scheme specified in the REntity annotation of the instance class.
*
* @param liveObjectId the liveObjectId to set
* @see org.redisson.core.RMap
*/
void setLiveObjectId(Object liveObjectId);
/**
* Returns true if this object holds no other values apart from the field
* annotated with RId. This involves in invoking the isExist() method on the
* underlying RMap. Since the field with RId annotation is encoded in the
* name of the underlying RMap, so to ensure the map exist in redis, set a
* non null value to any of the other fields.
*
* @return
* @see org.redisson.core.RMap
*/
boolean isExists();
/**
* Deletes the underlying RMap.
* @return
*/
boolean delete();
}

@ -0,0 +1,246 @@
/**
* 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.api;
/**
* The pre-registration of each entity class is not necessary.
*
* Entity's getters and setters operations gets redirected to Redis
* automatically.
*
* @author Rui Gu (https://github.com/jackygurui)
*
*/
public interface RLiveObjectService {
/**
* Find or create the entity from Redis with the id.
*
* The entityClass should have a field annotated with RId, and the
* entityClass itself should have REntity annotated. The type of the RId can
* be anything <b>except</b> the followings:
* <ol>
* <li>An array i.e. byte[], int[], Integer[], etc.</li>
* <li>or a RObject i.e. RedissonMap</li>
* <li>or a Class with REntity annotation.</li>
* </ol>
*
*
* @param entityClass Entity class
* @param <T> Entity type
* @return Always returns a proxied object. Even it does not exist in redis.
*/
<T> T create(Class<T> entityClass);
/**
* Finds the entity from Redis with the id.
*
* The entityClass should have a field annotated with RId, and the
* entityClass itself should have REntity annotated. The type of the RId can
* be anything <b>except</b> the followings:
* <ol>
* <li>An array i.e. byte[], int[], Integer[], etc.</li>
* <li>or a RObject i.e. RedissonMap</li>
* <li>or a Class with REntity annotation.</li>
* </ol>
*
*
* @param entityClass Entity class
* @param id identifier
* @param <T> Entity type
* @param <K> Key type
* @return a proxied object if it exists in redis, or null if not.
*/
<T, K> T get(Class<T> entityClass, K id);
/**
* Find or create the entity from Redis with the id.
*
* The entityClass should have a field annotated with RId, and the
* entityClass itself should have REntity annotated. The type of the RId can
* be anything <b>except</b> the followings:
* <ol>
* <li>An array i.e. byte[], int[], Integer[], etc.</li>
* <li>or a RObject i.e. RedissonMap</li>
* <li>or a Class with REntity annotation.</li>
* </ol>
*
*
* @param entityClass Entity class
* @param id identifier
* @param <T> Entity type
* @param <K> Key type
* @return Always returns a proxied object. Even it does not exist in redis.
*/
<T, K> T getOrCreate(Class<T> entityClass, K id);
/**
* Returns proxied object for the detached object. Discard all the
* field values already in the detached instance.
*
* The class representing this object should have a field annotated with
* RId, and the object should hold a non null value in that field.
*
* If this object is not in redis then a new <b>blank</b> proxied instance
* with the same RId field value will be created.
*
* @param <T> Entity type
* @param detachedObject
* @return
* @throws IllegalArgumentException if the object is is a RLiveObject instance.
*/
<T> T attach(T detachedObject);
/**
* Returns proxied object for the detached object. Transfers all the
* <b>NON NULL</b> field values to the redis server. It does not delete any
* existing data in redis in case of the field value is null.
*
* The class representing this object should have a field annotated with
* RId, and the object should hold a non null value in that field.
*
* If this object is not in redis then a new hash key will be created to
* store it.
*
* @param <T> Entity type
* @param detachedObject
* @return
* @throws IllegalArgumentException if the object is is a RLiveObject instance.
*/
<T> T merge(T detachedObject);
/**
* Returns proxied attached object for the detached object. Transfers all the
* <b>NON NULL</b> field values to the redis server. Only when the it does
* not already exist.
*
* The class representing this object should have a field annotated with
* RId, and the object should hold a non null value in that field.
*
* If this object is not in redis then a new hash key will be created to
* store it.
*
* @param <T> Entity type
* @param detachedObject
* @return
*/
<T> T persist(T detachedObject);
/**
* Returns unproxied detached object for the attached object.
*
* @param <T> Entity type
* @param attachedObject
* @return
*/
<T> T detach(T attachedObject);
/**
* Deletes attached object including all nested objects.
*
* @param <T> Entity type
* @param attachedObject
*/
<T> void delete(T attachedObject);
/**
* Deletes object by class and id including all nested objects.
*
* @param <T> Entity type
* @param <K> Key type
* @param entityClass
* @param id
*/
<T, K> void delete(Class<T> entityClass, K id);
/**
* To cast the instance to RLiveObject instance.
*
* @param <T>
* @param instance
* @return
*/
<T> RLiveObject asLiveObject(T instance);
/**
* Returns true if the instance is a instance of RLiveObject.
*
* @param <T>
* @param instance
* @return
*/
<T> boolean isLiveObject(T instance);
/**
* Returns true if the RLiveObject does not yet exist in redis. Also true if
* the passed object is not a RLiveObject.
*
* @param <T>
* @param instance
* @return
*/
<T> boolean isExists(T instance);
/**
* Pre register the class with the service, registering all the classes on
* startup can speed up the instance creation. This is <b>NOT</b> mandatory
* since the class will also be registered lazyly when it first is used.
*
* All classed registered with the service is stored in a class cache.
*
* The cache is independent between different RedissonClient instances. When
* a class is registered in one RLiveObjectService instance it is also
* accessible in another RLiveObjectService instance so long as they are
* created by the same RedissonClient instance.
*
* @param cls
*/
void registerClass(Class cls);
/**
* Unregister the class with the service. This is useful after you decide
* the class is no longer required.
*
* A class will be automatically unregistered if the service encountered any
* errors during proxying or creating the object, since those errors are not
* recoverable.
*
* All classed registered with the service is stored in a class cache.
*
* The cache is independent between different RedissonClient instances. When
* a class is registered in one RLiveObjectService instance it is also
* accessible in another RLiveObjectService instance so long as they are
* created by the same RedissonClient instance.
*
* @param cls It can be either the proxied class or the unproxied conterpart.
*/
void unregisterClass(Class cls);
/**
* Check if the class is registered in the cache.
*
* All classed registered with the service is stored in a class cache.
*
* The cache is independent between different RedissonClient instances. When
* a class is registered in one RLiveObjectService instance it is also
* accessible in another RLiveObjectService instance so long as they are
* created by the same RedissonClient instance.
*
* @param cls
* @return
*/
boolean isClassRegistered(Class cls);
}

@ -152,4 +152,7 @@ public class JsonJacksonCodec implements Codec {
return encoder;
}
public ObjectMapper getObjectMapper() {
return mapObjectMapper;
}
}

@ -15,6 +15,8 @@
*/
package org.redisson.core;
import org.redisson.client.codec.Codec;
/**
* Base interface for all Redisson objects
*
@ -76,4 +78,10 @@ public interface RObject extends RObjectAsync {
*/
boolean isExists();
/**
* Returns the underlying Codec used by this RObject
*
* @return
*/
Codec getCodec();
}

@ -0,0 +1,29 @@
/**
* 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;
import org.redisson.core.RMap;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class LiveObjectTemplate {
private Object liveObjectId;
private RMap liveObjectLiveMap;
}

@ -0,0 +1,45 @@
/**
* 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.annotation;
import org.redisson.liveobject.resolver.NamingScheme;
import org.redisson.liveobject.resolver.DefaultNamingScheme;
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.codec.JsonJacksonCodec;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface REntity {
public enum TransformationMode {
IMPLEMENTATION_BASED, ANNOTATION_BASED
}
Class<? extends NamingScheme> namingScheme() default DefaultNamingScheme.class;
Class<? extends Codec> codec() default JsonJacksonCodec.class;
TransformationMode fieldTransformation() default TransformationMode.ANNOTATION_BASED;
}

@ -0,0 +1,33 @@
/**
* 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.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.RandomUUIDIdStringGenerator;
import org.redisson.liveobject.resolver.RIdResolver;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface RId {
Class<? extends RIdResolver> generator() default RandomUUIDIdStringGenerator.class;
}

@ -0,0 +1,39 @@
/**
* 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.annotation;
import org.redisson.liveobject.resolver.NamingScheme;
import org.redisson.liveobject.resolver.DefaultNamingScheme;
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.codec.JsonJacksonCodec;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface RObjectField{
Class<? extends NamingScheme> namingScheme() default DefaultNamingScheme.class;
Class<? extends Codec> codec() default JsonJacksonCodec.class;
}

@ -0,0 +1,242 @@
/**
* 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.core;
import io.netty.util.internal.PlatformDependent;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.AbstractMap;
import java.util.BitSet;
import java.util.Collection;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
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.RedissonBitSet;
import org.redisson.RedissonBlockingDeque;
import org.redisson.RedissonBlockingQueue;
import org.redisson.RedissonClient;
import org.redisson.RedissonDeque;
import org.redisson.RedissonList;
import org.redisson.RedissonMap;
import org.redisson.RedissonQueue;
import org.redisson.RedissonReference;
import org.redisson.RedissonSet;
import org.redisson.RedissonSortedSet;
import org.redisson.client.codec.Codec;
import org.redisson.core.RMap;
import org.redisson.core.RObject;
import org.redisson.liveobject.provider.CodecProvider;
import org.redisson.api.RLiveObject;
import org.redisson.core.RBitSet;
import org.redisson.core.RObject;
import org.redisson.liveobject.provider.ResolverProvider;
import org.redisson.liveobject.resolver.NamingScheme;
import org.redisson.liveobject.annotation.REntity;
import org.redisson.liveobject.annotation.REntity.TransformationMode;
import org.redisson.liveobject.annotation.RId;
import org.redisson.liveobject.annotation.RObjectField;
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
* the proxied target class. That is one instance of this class per proxied
* class.
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class AccessorInterceptor {
private final RedissonClient redisson;
private final CodecProvider codecProvider;
private final ResolverProvider resolverProvider;
private final ConcurrentMap<String, NamingScheme> namingSchemeCache = PlatformDependent.newConcurrentHashMap();
private static final LinkedHashMap<Class, Class<? extends RObject>> supportedClassMapping;
public AccessorInterceptor(RedissonClient redisson, CodecProvider codecProvider, ResolverProvider resolverProvider) {
this.redisson = redisson;
this.codecProvider = codecProvider;
this.resolverProvider = resolverProvider;
}
static {
supportedClassMapping = new LinkedHashMap<Class, Class<? extends RObject>>();
supportedClassMapping.put(SortedSet.class, RedissonSortedSet.class);
supportedClassMapping.put(Set.class, RedissonSet.class);
supportedClassMapping.put(ConcurrentMap.class, RedissonMap.class);
supportedClassMapping.put(Map.class, RedissonMap.class);
supportedClassMapping.put(BlockingDeque.class, RedissonBlockingDeque.class);
supportedClassMapping.put(Deque.class, RedissonDeque.class);
supportedClassMapping.put(BlockingQueue.class, RedissonBlockingQueue.class);
supportedClassMapping.put(Queue.class, RedissonQueue.class);
supportedClassMapping.put(List.class, RedissonList.class);
supportedClassMapping.put(BitSet.class, RedissonBitSet.class);
}
@RuntimeType
public Object intercept(@Origin Method method, @SuperCall Callable<?> superMethod,
@AllArguments Object[] args, @This Object me,
@FieldValue("liveObjectLiveMap") RMap liveMap) throws Exception {
if (isGetter(method, getREntityIdFieldName(me))) {
return ((RLiveObject) me).getLiveObjectId();
}
if (isSetter(method, getREntityIdFieldName(me))) {
((RLiveObject) me).setLiveObjectId(args[0]);
return null;
}
String fieldName = getFieldName(method);
if (isGetter(method, fieldName)) {
Object result = liveMap.get(fieldName);
if (result instanceof RedissonReference) {
return RedissonObjectFactory.create(redisson, codecProvider, resolverProvider, (RedissonReference) result, method.getReturnType());
}
return result;
}
if (isSetter(method, fieldName)) {
Class idFieldType = me.getClass().getSuperclass().getDeclaredField(fieldName).getType();
if (args[0].getClass().getSuperclass().isAnnotationPresent(REntity.class)) {
Class<? extends Object> rEntity = args[0].getClass().getSuperclass();
REntity anno = rEntity.getAnnotation(REntity.class);
NamingScheme ns = anno.namingScheme()
.getDeclaredConstructor(Codec.class)
.newInstance(codecProvider.getCodec(anno, (Class) rEntity));
liveMap.put(fieldName, new RedissonReference(rEntity,
ns.getName(rEntity, idFieldType, getREntityIdFieldName(args[0]),
((RLiveObject) args[0]).getLiveObjectId())));
return me;
}
Object arg = args[0];
if (!(arg instanceof RObject)
&& (arg instanceof BitSet || arg instanceof Collection || arg instanceof Map)
&& TransformationMode.ANNOTATION_BASED
.equals(me.getClass().getSuperclass()
.getAnnotation(REntity.class).fieldTransformation())) {
Class<? extends RObject> mappedClass = getMappedClass(arg);
if (mappedClass != null) {
Entry<NamingScheme, Codec> entry = getFieldNamingSchemeAndCodec(me.getClass().getSuperclass(), mappedClass, fieldName);
RObject obj = RedissonObjectFactory
.create(redisson,
mappedClass,
entry.getKey().getFieldReferenceName(me.getClass().getSuperclass(),
idFieldType,
getREntityIdFieldName(me),
((RLiveObject) me).getLiveObjectId(),
mappedClass,
fieldName,
arg),
entry.getValue());
if (obj instanceof RBitSet) {
((RBitSet) obj).set((BitSet) args[0]);
} else if (obj instanceof Collection) {
((Collection) obj).addAll((Collection) arg);
} else {
((Map) obj).putAll((Map) arg);
}
arg = obj;
}
}
if (arg instanceof RObject) {
RObject ar = (RObject) arg;
Codec codec = ar.getCodec();
codecProvider.registerCodec((Class) codec.getClass(), ar, codec);
liveMap.put(fieldName,
new RedissonReference(ar.getClass(), ar.getName(), codec));
return me;
}
liveMap.put(fieldName, args[0]);
return me;
}
return superMethod.call();
}
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));
}
/**
* WARNING: rEntity has to be the class of @This object.
*/
private Entry<NamingScheme, Codec> getFieldNamingSchemeAndCodec(Class<?> rEntity, Class<? extends RObject> rObjectClass, String fieldName) throws Exception {
Codec c;
Field field = rEntity.getDeclaredField(fieldName);
if (field.isAnnotationPresent(RObjectField.class)) {
RObjectField anno = field.getAnnotation(RObjectField.class);
c = codecProvider.getCodec(anno, rEntity, rObjectClass, fieldName);
if (!namingSchemeCache.containsKey(fieldName)) {
namingSchemeCache.putIfAbsent(fieldName, anno.namingScheme()
.getDeclaredConstructor(Codec.class)
.newInstance(c));
}
} else {
REntity anno = rEntity.getAnnotation(REntity.class);
c = codecProvider.getCodec(anno, (Class) rEntity);
if (!namingSchemeCache.containsKey(fieldName)) {
namingSchemeCache.putIfAbsent(fieldName, anno.namingScheme()
.getDeclaredConstructor(Codec.class)
.newInstance(c));
}
}
AbstractMap.SimpleImmutableEntry entry = new AbstractMap.SimpleImmutableEntry(namingSchemeCache.get(fieldName), c);
return entry;
}
private static String getFieldNameSuffix(String fieldName) {
return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
}
private static String getREntityIdFieldName(Object o) throws Exception {
return Introspectior
.getFieldsWithAnnotation(o.getClass().getSuperclass(), RId.class)
.getOnly()
.getName();
}
private static Class<? extends RObject> getMappedClass(Object obj) {
for (Entry<Class, Class<? extends RObject>> entrySet : supportedClassMapping.entrySet()) {
if (entrySet.getKey().isInstance(obj)) {
return entrySet.getValue();
}
}
return null;
}
}

@ -0,0 +1,43 @@
/**
* 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.core;
import java.lang.reflect.Method;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import org.redisson.core.RMap;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class ExpirableInterceptor {
@RuntimeType
public static Object intercept(
@Origin Method method,
@AllArguments Object[] args,
@FieldValue("liveObjectLiveMap") RMap map
) throws Exception {
Class[] cls = new Class[args.length];
for (int i = 0; i < args.length; i++) {
cls[i] = args[i].getClass();
}
return RMap.class.getMethod(method.getName(), cls).invoke(map, args);
}
}

@ -0,0 +1,137 @@
/**
* 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.core;
import java.lang.reflect.Method;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.FieldProxy;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.This;
import org.redisson.RedissonClient;
import org.redisson.RedissonMap;
import org.redisson.client.RedisException;
import org.redisson.client.codec.Codec;
import org.redisson.core.RMap;
import org.redisson.liveobject.provider.CodecProvider;
import org.redisson.liveobject.resolver.NamingScheme;
import org.redisson.liveobject.annotation.REntity;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class LiveObjectInterceptor {
public interface Getter {
Object getValue();
}
public interface Setter {
void setValue(Object value);
}
private final RedissonClient redisson;
private final CodecProvider codecProvider;
private final Class originalClass;
private final String idFieldName;
private final Class idFieldType;
private final NamingScheme namingScheme;
private final Class<? extends Codec> codecClass;
public LiveObjectInterceptor(RedissonClient redisson, CodecProvider codecProvider, Class entityClass, String idFieldName) {
this.redisson = redisson;
this.codecProvider = codecProvider;
this.originalClass = entityClass;
this.idFieldName = idFieldName;
REntity anno = (REntity) entityClass.getAnnotation(REntity.class);
this.codecClass = anno.codec();
try {
this.namingScheme = anno.namingScheme().getDeclaredConstructor(Codec.class).newInstance(codecProvider.getCodec(anno, originalClass));
this.idFieldType = originalClass.getDeclaredField(idFieldName).getType();
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
@RuntimeType
public Object intercept(
@Origin Method method,
@AllArguments Object[] args,
@This Object me,
@FieldValue("liveObjectId") Object id,
@FieldProxy("liveObjectId") Setter idSetter,
@FieldProxy("liveObjectId") Getter idGetter,
@FieldValue("liveObjectLiveMap") RMap map,
@FieldProxy("liveObjectLiveMap") Setter mapSetter,
@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) {
if (map.getName().equals(idKey)) {
return null;
}
try {
map.rename(getMapKey(args[0]));
} catch (RedisException e) {
if (e.getMessage() == null || !e.getMessage().startsWith("ERR no such key")) {
throw e;
}
//key may already renamed by others.
}
}
RMap<Object, Object> liveMap = redisson.getMap(idKey,
codecProvider.getCodec(codecClass, RedissonMap.class, idKey));
mapSetter.setValue(liveMap);
return null;
}
if ("getLiveObjectId".equals(method.getName())) {
if (map == null) {
return null;
}
return namingScheme.resolveId(map.getName());
}
if ("getLiveObjectLiveMap".equals(method.getName())) {
return map;
}
if ("isExists".equals(method.getName())) {
return map.isExists();
}
if ("delete".equals(method.getName())) {
return map.delete();
}
throw new NoSuchMethodException();
}
private String getMapKey(Object id) {
return namingScheme.getName(originalClass, idFieldType, idFieldName, id);
}
}

@ -0,0 +1,60 @@
/**
* 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.annotation.Annotation;
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 Rui Gu (https://github.com/jackygurui)
*/
public class Introspectior {
public static TypeDescription.ForLoadedType getTypeDescription(Class c) {
return new TypeDescription.ForLoadedType(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,86 @@
/**
* 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 java.util.Arrays;
import java.util.List;
import org.redisson.RedissonClient;
import org.redisson.RedissonReference;
import org.redisson.client.codec.Codec;
import org.redisson.core.RBitSet;
import org.redisson.core.RObject;
import org.redisson.liveobject.provider.CodecProvider;
import org.redisson.liveobject.provider.ResolverProvider;
import org.redisson.liveobject.resolver.NamingScheme;
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, ResolverProvider resolverProvider, 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);
NamingScheme ns = anno.namingScheme()
.getDeclaredConstructor(Codec.class)
.newInstance(codecProvider.getCodec(anno, rr.getType()));
return (T) redisson.getLiveObjectService(codecProvider, resolverProvider).getOrCreate(type, ns.resolveId(rr.getKeyName()));
}
List<Class<?>> interfaces = Arrays.asList(rr.getType().getInterfaces());
for (Method method : RedissonClient.class.getDeclaredMethods()) {
if (method.getName().startsWith("get")
&& method.getReturnType().isAssignableFrom(type)
&& expected.isAssignableFrom(method.getReturnType())
&& interfaces.contains(method.getReturnType())) {
if ((rr.isDefaultCodec() || RBitSet.class.isAssignableFrom(method.getReturnType())) && 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());
}
public static <T extends RObject, K extends Codec> T create(RedissonClient redisson, Class<T> expectedType, String name, K codec) throws Exception {
List<Class<?>> interfaces = Arrays.asList(expectedType.getInterfaces());
for (Method method : RedissonClient.class.getDeclaredMethods()) {
if (method.getName().startsWith("get")
&& method.getReturnType().isAssignableFrom(expectedType)
&& interfaces.contains(method.getReturnType())) {
if ((codec == null || RBitSet.class.isAssignableFrom(method.getReturnType())) && method.getParameterCount() == 1) {
return (T) method.invoke(redisson, name);
} else if (codec != null
&& method.getParameterCount() == 2
&& String.class.equals(method.getParameterTypes()[0])
&& Codec.class.equals(method.getParameterTypes()[1])) {
return (T) method.invoke(redisson, name, codec);
}
}
}
throw new ClassNotFoundException("No RObject is found to match class type of " + (expectedType != null ? expectedType.getName() : "null") + " with codec type of " + (codec != null ? codec.getClass().getName() : "null"));
}
}

@ -0,0 +1,179 @@
/**
* 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.provider;
import org.redisson.client.codec.Codec;
import org.redisson.core.RObject;
import org.redisson.liveobject.annotation.REntity;
import org.redisson.liveobject.annotation.RObjectField;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public interface CodecProvider {
/**
* Get codec instance by its class.
*
* @param <T> the expected codec type.
* @param codecClass the codec class used to lookup the codec.
* @return the cached codec instance.
*/
<T extends Codec> T getCodec(Class<T> codecClass);
/**
* Get a codec instance by a REntity annotation and the class annotated with
* it.
*
* @param <T> the expected codec type.
* @param anno REntity annotation used on the class.
* @param cls The class that has the REntity annotation.
* @return the cached codec instance.
*/
<T extends Codec> T getCodec(REntity anno, Class<?> cls);
/**
* Get a codec instance by a RObjectField annotation and the class annotated
* with REntity, the implementation class of RObject the field is going to
* be transformed into and the name of the field with this RObjectField
* annotation.
*
* @param <T> the expected codec type.
* @param <K> the type of the RObject.
* @param anno RObjectField annotation used on the field.
* @param cls The class that has the REntity annotation.
* @param rObjectClass the implementation class of RObject the field is going
* to be transformed into.
* @param fieldName the name of the field with this RObjectField annotation.
* @return the cached codec instance.
*/
<T extends Codec, K extends RObject> T getCodec(RObjectField anno, Class<?> cls, Class<K> rObjectClass, String fieldName);
/**
* Get a codec instance by its class, the implementation class of the RObject
* interface.
*
* @param <T> the expected codec type.
* @param <K> the RObject type.
* @param codecClass the codec class used to lookup the codec.
* @param rObjectClass the class of the RObject implementation.
* @return the cached codec instance.
*/
<T extends Codec, K extends RObject> T getCodec(Class<T> codecClass, Class<K> rObjectClass);
/**
* Get a codec instance by its class, the implementation class of the RObject
* interface and the name of RObject (the value returned by RObject.getName()
* method).
*
* @param <T> the expected codec type.
* @param <K> the RObject type.
* @param codecClass the codec class used to lookup the codec.
* @param rObjectClass the class of the RObject implementation.
* @param name the name of RObject.
* @return the cached codec instance.
*/
<T extends Codec, K extends RObject> T getCodec(Class<T> codecClass, Class<K> rObjectClass, String name);
/**
* Get a codec instance by its class and an instance of the RObject.
*
* @param <T> the expected codec type.
* @param codecClass the codec class used to lookup the codec.
* @param rObject instance of the RObject implementation.
* @return the cached codec instance.
*/
<T extends Codec> T getCodec(Class<T> codecClass, RObject rObject);
/**
* Register a codec by its class or super class.
*
* @param <T> the codec type to register.
* @param codecClass the codec Class to register it can be a super class of
* the instance.
* @param codec the codec instance.
*/
<T extends Codec> void registerCodec(Class<T> codecClass, T codec);
/**
* Register a codec by the REntity annotation and the class annotated with
* it.
*
* @param <T> the codec type to register.
* @param anno REntity annotation used on the class.
* @param cls The class that has the REntity annotation.
* @param codec the codec instance.
*/
<T extends Codec> void registerCodec(REntity anno, Class<?> cls, T codec);
/**
* Register a codec by the RObjectField annotation, the class annotated with
* REntity, the implementation class of RObject the field is going to
* be transformed into and the name of the field with this RObjectField
* annotation.
*
* @param <T> the codec type to register.
* @param <K> the type of the RObject.
* @param anno RObjectField annotation used on the field.
* @param cls The class that has the REntity annotation.
* @param rObjectClass the implementation class of RObject the field is going
* to be transformed into.
* @param fieldName the name of the field with this RObjectField annotation.
* @param codec the codec instance.
*/
<T extends Codec, K extends RObject> void registerCodec(RObjectField anno, Class<?> cls, Class<K> rObjectClass, String fieldName, T codec);
/**
* Register a codec by its class or super class and the class of the RObject
* implementation.
*
* @param <T> the codec type to register.
* @param <K> the RObjectField type.
* @param codecClass the codec Class to register it can be a super class of
* the instance.
* @param rObjectClass the class of the RObject implementation.
* @param codec the codec instance.
*/
<T extends Codec, K extends RObject> void registerCodec(Class<T> codecClass, Class<K> rObjectClass, T codec);
/**
* Register a codec by its class or super class, the class of the RObject
* implementation and the name of RObject (the value returned by
* RObjectField.getName() method).
*
* @param <T> the codec type to register.
* @param <K> the RObjectField type.
* @param codecClass the codec Class to register it can be a super class of
* the instance.
* @param rObjectClass the class of the RObject implementation.
* @param name the name of RObject.
* @param codec the codec instance.
*/
<T extends Codec, K extends RObject> void registerCodec(Class<T> codecClass, Class<K> rObjectClass, String name, T codec);
/**
* Register a codec by its class or super class and an instance of the
* RObject.
*
* @param <T> the codec type to register.
* @param codecClass the codec Class to register it can be a super class of
* the instance.
* @param rObject instance of the RObject implementation.
* @param codec the codec instance.
*/
<T extends Codec> void registerCodec(Class<T> codecClass, RObject rObject, T codec);
}

@ -0,0 +1,138 @@
/**
* 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.provider;
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;
import org.redisson.liveobject.annotation.RObjectField;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class DefaultCodecProvider implements CodecProvider {
public final ConcurrentMap<Class<? extends Codec>, Codec> codecCache = PlatformDependent.newConcurrentHashMap();
@Override
public <T extends Codec> T getCodec(Class<T> codecClass) {
if (!codecCache.containsKey(codecClass)) {
try {
codecCache.putIfAbsent(codecClass, codecClass.newInstance());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
return (T) codecCache.get(codecClass);
}
@Override
public <T extends Codec> T getCodec(REntity anno, Class<?> cls) {
if (!cls.isAnnotationPresent(anno.annotationType())) {
throw new IllegalArgumentException("Annotation REntity does not present on type [" + cls.getCanonicalName() + "]");
}
return this.<T>getCodec((Class<T>) anno.codec());
}
@Override
public <T extends Codec, K extends RObject> T getCodec(RObjectField anno, Class<?> cls, Class<K> rObjectClass, String fieldName) {
try {
if (!cls.getField(fieldName).isAnnotationPresent(anno.getClass())) {
throw new IllegalArgumentException("Annotation RObjectField does not present on field " + fieldName + " of type [" + cls.getCanonicalName() + "]");
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
if (rObjectClass.isInterface()) {
throw new IllegalArgumentException("Cannot lookup an interface class of RObject [" + rObjectClass.getCanonicalName() + "]. Concrete class only.");
}
return this.<T>getCodec((Class<T>) anno.codec());
}
@Override
public <T extends Codec, K extends RObject> T getCodec(Class<T> codecClass, Class<K> rObjectClass) {
if (rObjectClass.isInterface()) {
throw new IllegalArgumentException("Cannot lookup an interface class of RObject [" + rObjectClass.getCanonicalName() + "]. Concrete class only.");
}
return getCodec(codecClass);
}
@Override
public <T extends Codec, K extends RObject> T getCodec(Class<T> codecClass, Class<K> rObjectClass, String name) {
if (rObjectClass.isInterface()) {
throw new IllegalArgumentException("Cannot lookup an interface class of RObject [" + rObjectClass.getCanonicalName() + "]. Concrete class only.");
}
return getCodec(codecClass, rObjectClass);
}
@Override
public <T extends Codec> T getCodec(Class<T> codecClass, RObject rObject) {
return getCodec(codecClass, rObject.getClass(), rObject.getName());
}
@Override
public <T extends Codec> void registerCodec(Class<T> cls, T codec) {
if (!cls.isInstance(codec)) {
throw new IllegalArgumentException("codec is not an instance of the class [" + cls.getCanonicalName() + "]");
}
codecCache.putIfAbsent(cls, codec);
}
@Override
public <T extends Codec> void registerCodec(REntity anno, Class<?> cls, T codec) {
if (!cls.isAnnotationPresent(anno.getClass())) {
throw new IllegalArgumentException("Annotation REntity does not present on type [" + cls.getCanonicalName() + "]");
}
registerCodec((Class<Codec>) anno.codec(), codec);
}
@Override
public <T extends Codec, K extends RObject> void registerCodec(RObjectField anno, Class<?> cls, Class<K> rObjectClass, String fieldName, T codec) {
try {
if (!cls.getField(fieldName).isAnnotationPresent(anno.getClass())) {
throw new IllegalArgumentException("Annotation RObjectField does not present on field " + fieldName + " of type [" + cls.getCanonicalName() + "]");
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
if (rObjectClass.isInterface()) {
throw new IllegalArgumentException("Cannot lookup an interface class of RObject [" + rObjectClass.getCanonicalName() + "]. Concrete class only.");
}
registerCodec((Class<Codec>) anno.codec(), codec);
}
@Override
public <T extends Codec, K extends RObject> void registerCodec(Class<T> codecClass, Class<K> rObjectClass, T codec) {
if (rObjectClass.isInterface()) {
throw new IllegalArgumentException("Cannot register an interface class of RObject [" + rObjectClass.getCanonicalName() + "]. Concrete class only.");
}
registerCodec((Class<Codec>) codecClass, codec);
}
@Override
public <T extends Codec, K extends RObject> void registerCodec(Class<T> codecClass, Class<K> rObjectClass, String name, T codec) {
registerCodec(codecClass, rObjectClass, codec);
}
@Override
public <T extends Codec> void registerCodec(Class<T> codecClass, RObject rObject, T codec) {
registerCodec(codecClass, rObject.getClass(), rObject.getName(), codec);
}
}

@ -0,0 +1,47 @@
/**
* 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.provider;
import io.netty.util.internal.PlatformDependent;
import java.lang.annotation.Annotation;
import java.util.concurrent.ConcurrentMap;
import org.redisson.liveobject.resolver.Resolver;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class DefaultResolverProvider implements ResolverProvider {
public final ConcurrentMap<Class<? extends Resolver>, Resolver> providerCache = PlatformDependent.newConcurrentHashMap();
@Override
public Resolver getResolver(Class<?> cls, Class<? extends Resolver> resolverClass, Annotation anno) {
if (!providerCache.containsKey(resolverClass)) {
try {
providerCache.putIfAbsent(resolverClass, resolverClass.newInstance());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
return providerCache.get(resolverClass);
}
@Override
public void registerResolver(Class<?> cls, Class<? extends Resolver> resolverClass, Resolver resolver) {
providerCache.putIfAbsent(resolverClass, resolver);
}
}

@ -0,0 +1,30 @@
/**
* 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.provider;
import java.lang.annotation.Annotation;
import org.redisson.liveobject.resolver.Resolver;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public interface ResolverProvider {
Resolver getResolver(Class<?> cls, Class<? extends Resolver> resolverClass, Annotation anno);
void registerResolver(Class<?> cls, Class<? extends Resolver> resolverClass, Resolver resolver);
}

@ -0,0 +1,32 @@
/**
* 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.resolver;
import org.redisson.client.codec.Codec;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public abstract class AbstractNamingScheme implements NamingScheme {
protected final Codec codec;
public AbstractNamingScheme(Codec codec) {
this.codec = codec;
}
}

@ -0,0 +1,101 @@
/**
* 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.resolver;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.io.IOException;
import org.redisson.client.codec.Codec;
import org.redisson.client.handler.State;
import org.redisson.codec.JsonJacksonCodec;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class DefaultNamingScheme extends AbstractNamingScheme implements NamingScheme {
public static final DefaultNamingScheme INSTANCE = new DefaultNamingScheme(new JsonJacksonCodec());
private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
public DefaultNamingScheme(Codec codec) {
super(codec);
}
@Override
public String getName(Class entityClass, Class idFieldClass, String idFieldName, Object idValue) {
try {
String encode = bytesToHex(codec.getMapKeyEncoder().encode(idValue));
return "redisson_live_object:{"+ encode + "}:" + entityClass.getName() + ":" + idFieldName + ":" + idFieldClass.getName();
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to encode \"" + idFieldName + "\" [" + idValue + "] into byte[]", ex);
}
}
@Override
public String getFieldReferenceName(Class entityClass, Class idFieldClass, String idFieldName, Object idValue, Class cls, String fieldName, Object fieldValue) {
try {
String encode = bytesToHex(codec.getMapKeyEncoder().encode(idValue));
return "redisson_live_object_field:{" + encode + "}:" + entityClass.getName() + ":" + fieldName + ":" + cls.getName();
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to encode \"" + fieldName + "\" [" + fieldValue + "] into byte[]", ex);
}
}
@Override
public String resolveClassName(String name) {
return name.substring(name.lastIndexOf("}:") + 2, name.indexOf(":"));
}
@Override
public String resolveIdFieldName(String name) {
String s = name.substring(0, name.lastIndexOf(":"));
return s.substring(s.lastIndexOf(":") + 1);
}
@Override
public Object resolveId(String name) {
String decode = name.substring(name.indexOf("{") + 1, name.indexOf("}"));
ByteBuf b = Unpooled.wrappedBuffer(hexToBytes(decode));
try {
return codec.getMapKeyDecoder().decode(b, new State(false));
} catch (IOException ex) {
throw new IllegalStateException("Unable to decode [" + decode + "] into object", ex);
} finally {
b.release();
}
}
private 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);
}
private 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;
}
}

@ -0,0 +1,37 @@
/**
* 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.resolver;
import org.redisson.RedissonClient;
import org.redisson.liveobject.annotation.RId;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class DistributedAtomicLongIdGenerator implements RIdResolver<RId, Long> {
public static final DistributedAtomicLongIdGenerator INSTANCE
= new DistributedAtomicLongIdGenerator();
@Override
public Long resolve(Class value, RId id, String idFieldName, RedissonClient redisson) {
return redisson.getAtomicLong(this.getClass().getCanonicalName()
+ "{" + value.getCanonicalName() + "}:" + idFieldName)
.incrementAndGet();
}
}

@ -0,0 +1,34 @@
/**
* 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.resolver;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public interface NamingScheme {
public String getName(Class entityClass, Class idFieldClass, String idFieldName, Object idValue);
public String getFieldReferenceName(Class entityClass, Class idFieldClass, String idFieldName, Object idValue, Class cls, String fieldName, Object fieldValue);
public String resolveClassName(String name);
public String resolveIdFieldName(String name);
public Object resolveId(String name);
}

@ -0,0 +1,40 @@
/**
* 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.resolver;
import org.redisson.RedissonClient;
import org.redisson.liveobject.annotation.RId;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
* @param <A> RId annotation to resolve
* @param <V> Value type
*/
public interface RIdResolver<A extends RId, V> extends Resolver<Class, A, V>{
/**
* RLiveObjectService instantiate the class and invokes this method to get
* a value used as the value for the field with RId annotation.
*
* @param cls the class of the LiveObject.
* @param annotation the RId annotation used in the class.
* @param redisson
* @return resolved RId field value.
*/
public V resolve(Class cls, A annotation, String idFieldName, RedissonClient redisson);
}

@ -0,0 +1,35 @@
/**
* 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.resolver;
import java.util.UUID;
import org.redisson.RedissonClient;
import org.redisson.liveobject.annotation.RId;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class RandomUUIDIdStringGenerator implements RIdResolver<RId, String>{
public static final RandomUUIDIdStringGenerator INSTANCE = new RandomUUIDIdStringGenerator();
@Override
public String resolve(Class value, RId id, String idFieldName, RedissonClient redisson) {
return UUID.randomUUID().toString();
}
}

@ -0,0 +1,32 @@
/**
* 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.resolver;
import java.lang.annotation.Annotation;
import org.redisson.RedissonClient;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
* @param <T> Field instance
* @param <A> Annotation to resolve
* @param <V> Value type
*/
public interface Resolver<T, A extends Annotation, V> {
public V resolve(T value, A annotation, String idFieldName, RedissonClient redisson);
}

@ -94,7 +94,7 @@ public abstract class BaseConcurrentTest extends BaseTest {
}
pool.shutdown();
Assert.assertTrue(pool.awaitTermination(RedissonRuntimeEnvironment.isTravis ? 10 : 3, TimeUnit.MINUTES));
Assert.assertTrue(pool.awaitTermination(RedissonRuntimeEnvironment.isTravis ? 20 : 3, TimeUnit.MINUTES));
System.out.println(System.currentTimeMillis() - watch);

@ -19,6 +19,10 @@ import org.redisson.client.RedisConnection;
import org.redisson.client.protocol.RedisStrictCommand;
import org.redisson.client.protocol.convertor.VoidReplayConvertor;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class RedisRunner {
public enum REDIS_OPTIONS {

@ -5,7 +5,7 @@ import java.util.regex.Pattern;
/**
*
* @author Jack
* @author Rui Gu (https://github.com/jackygurui)
*/
public class RedisVersion implements Comparable<RedisVersion>{

@ -0,0 +1,885 @@
package org.redisson;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import static org.junit.Assert.*;
import org.junit.Test;
import org.redisson.core.RMap;
import org.redisson.api.RLiveObjectService;
import org.redisson.api.RLiveObject;
import org.redisson.core.RAtomicLong;
import org.redisson.core.RBitSet;
import org.redisson.core.RBlockingDeque;
import org.redisson.core.RBlockingQueue;
import org.redisson.core.RDeque;
import org.redisson.core.RList;
import org.redisson.core.RQueue;
import org.redisson.core.RSet;
import org.redisson.core.RSortedSet;
import org.redisson.liveobject.resolver.DefaultNamingScheme;
import org.redisson.liveobject.annotation.REntity;
import org.redisson.liveobject.annotation.RId;
import org.redisson.liveobject.resolver.DistributedAtomicLongIdGenerator;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class RedissonLiveObjectServiceTest 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;
}
}
@REntity
public static class TestREntityWithRMap implements Comparable<TestREntityWithRMap>, Serializable {
@RId
private String name;
private RMap value;
public TestREntityWithRMap(String name) {
this.name = name;
}
public TestREntityWithRMap(String name, RMap value) {
super();
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public RMap getValue() {
return value;
}
public void setName(String name) {
this.name = name;
}
public void setValue(RMap value) {
this.value = value;
}
@Override
public int compareTo(TestREntityWithRMap o) {
int res = name.compareTo(o.name);
if (res == 0 || value != null || o.value != null) {
if (value.getName() == null) {
return -1;
}
return value.getName().compareTo(o.value.getName());
}
return res;
}
}
@REntity
public static class TestREntityWithMap implements Comparable<TestREntityWithMap>, Serializable {
@RId
private String name;
private Map value;
public TestREntityWithMap(String name) {
this.name = name;
}
public TestREntityWithMap(String name, Map value) {
super();
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public Map getValue() {
return value;
}
public void setName(String name) {
this.name = name;
}
public void setValue(Map value) {
this.value = value;
}
@Override
public int compareTo(TestREntityWithMap o) {
return name.compareTo(o.name);
}
}
@REntity
public static class TestREntityIdNested implements Comparable<TestREntityIdNested>, Serializable {
@RId
private TestREntity name;
private String value;
public TestREntityIdNested(TestREntity name) {
this.name = name;
}
public TestREntityIdNested(TestREntity name, String value) {
super();
this.name = name;
this.value = value;
}
public TestREntity getName() {
return name;
}
public String getValue() {
return value;
}
public void setName(TestREntity name) {
this.name = name;
}
public void setValue(String value) {
this.value = value;
}
@Override
public int compareTo(TestREntityIdNested o) {
int res = name.compareTo(o.name);
if (res == 0 || value != null || o.value != null) {
return value.compareTo(o.value);
}
return res;
}
}
@REntity
public static class TestREntityValueNested implements Comparable<TestREntityValueNested>, Serializable {
@RId
private String name;
private TestREntityWithRMap value;
public TestREntityValueNested(String name) {
this.name = name;
}
public TestREntityValueNested(String name, TestREntityWithRMap value) {
super();
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public TestREntityWithRMap getValue() {
return value;
}
public void setName(String name) {
this.name = name;
}
public void setValue(TestREntityWithRMap value) {
this.value = value;
}
@Override
public int compareTo(TestREntityValueNested o) {
int res = name.compareTo(o.name);
if (res == 0 || value != null || o.value != null) {
return value.compareTo(o.value);
}
return res;
}
}
@Test
public void testBasics() {
RLiveObjectService s = redisson.getLiveObjectService();
TestREntity t = s.<TestREntity, String>getOrCreate(TestREntity.class, "1");
assertEquals("1", t.getName());
assertTrue(!redisson.getMap(DefaultNamingScheme.INSTANCE.getName(TestREntity.class, String.class, "name", "1")).isExists());
t.setName("3333");
assertEquals("3333", t.getName());
assertTrue(!redisson.getMap(DefaultNamingScheme.INSTANCE.getName(TestREntity.class, String.class, "name", "3333")).isExists());
t.setValue("111");
assertEquals("111", t.getValue());
assertTrue(redisson.getMap(DefaultNamingScheme.INSTANCE.getName(TestREntity.class, String.class, "name", "3333")).isExists());
assertTrue(!redisson.getMap(DefaultNamingScheme.INSTANCE.getName(TestREntity.class, String.class, "name", "1")).isExists());
assertEquals("111", redisson.getMap(DefaultNamingScheme.INSTANCE.getName(TestREntity.class, String.class, "name", "3333")).get("value"));
// ((RLiveObject) t).getLiveObjectLiveMap().put("value", "555");
// assertEquals("555", redisson.getMap(REntity.DefaultNamingScheme.INSTANCE.getName(TestREntity.class, "name", "3333")).get("value"));
// assertEquals("3333", ((RObject) t).getName());//field access takes priority over the implemented interface.
}
@Test
public void testLiveObjectWithCollection() {
RLiveObjectService s = redisson.getLiveObjectService();
TestREntityWithMap t = s.<TestREntityWithMap, String>getOrCreate(TestREntityWithMap.class, "2");
RMap<String, String> map = redisson.<String, String>getMap("testMap");
t.setValue(map);
map.put("field", "123");
assertEquals("123",
s.<TestREntityWithMap, String>getOrCreate(TestREntityWithMap.class, "2")
.getValue().get("field"));
s.getOrCreate(TestREntityWithMap.class, "2").getValue().put("field", "333");
assertEquals("333",
s.<TestREntityWithMap, String>getOrCreate(TestREntityWithMap.class, "2")
.getValue().get("field"));
HashMap<String, String> map2 = new HashMap<>();
map2.put("field", "hello");
t.setValue(map2);
assertEquals("hello",
s.<TestREntityWithMap, String>getOrCreate(TestREntityWithMap.class, "2")
.getValue().get("field"));
}
@Test
public void testLiveObjectWithRObject() {
RLiveObjectService s = redisson.getLiveObjectService();
TestREntityWithRMap t = s.<TestREntityWithRMap, String>getOrCreate(TestREntityWithRMap.class, "2");
RMap<String, String> map = redisson.<String, String>getMap("testMap");
t.setValue(map);
map.put("field", "123");
assertEquals("123",
s.<TestREntityWithRMap, String>getOrCreate(TestREntityWithRMap.class, "2")
.getValue().get("field"));
s.getOrCreate(TestREntityWithRMap.class, "2").getValue().put("field", "333");
assertEquals("333",
s.<TestREntityWithRMap, String>getOrCreate(TestREntityWithRMap.class, "2")
.getValue().get("field"));
}
@Test
public void testLiveObjectWithNestedLiveObjectAsId() {
RLiveObjectService s = redisson.getLiveObjectService();
TestREntity t1 = s.<TestREntity, String>getOrCreate(TestREntity.class, "1");
try {
s.<TestREntityIdNested, TestREntity>getOrCreate(TestREntityIdNested.class, t1);
fail("Should not be here");
} catch (Exception e) {
assertEquals("Field with RId annotation cannot be a type of which class is annotated with REntity.", e.getCause().getMessage());
}
}
@Test
public void testLiveObjectWithNestedLiveObjectAsValue() throws Exception {
RLiveObjectService s = redisson.getLiveObjectService();
TestREntityWithRMap t1 = s.<TestREntityWithRMap, String>getOrCreate(TestREntityWithRMap.class, "111");
TestREntityValueNested t2 = s.<TestREntityValueNested, String>getOrCreate(TestREntityValueNested.class, "122");
RMap<String, String> map = redisson.<String, String>getMap("32123");
t2.setValue(t1);
t2.getValue().setValue(map);
map.put("field", "123");
assertEquals("123",
s.<TestREntityWithRMap, String>getOrCreate(TestREntityWithRMap.class, "111")
.getValue().get("field"));
assertEquals("123",
s.<TestREntityValueNested, String>getOrCreate(TestREntityValueNested.class, "122")
.getValue().getValue().get("field"));
}
@REntity
public static class TestClass {
private String value;
private String code;
private Object content;
@RId
private Serializable id;
public TestClass(Serializable id) {
this.id = id;
}
public Serializable getId() {
return id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
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 {
private int id;
public ObjectId() {
}
public ObjectId(int id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ObjectId)) {
return false;
}
return id == ((ObjectId) obj).id;
}
@Override
public int hashCode() {
int hash = 3;
hash = 11 * hash + this.id;
return hash;
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
@Override
public String toString() {
return "" + id;
}
}
@Test
public void testSerializerable() {
RLiveObjectService service = redisson.getLiveObjectService();
TestClass t = service.getOrCreate(TestClass.class, "55555");
assertTrue(Objects.equals("55555", t.getId()));
t = service.getOrCreate(TestClass.class, 90909l);
assertTrue(Objects.equals(90909l, t.getId()));
t = service.getOrCreate(TestClass.class, 90909);
assertTrue(Objects.equals(90909, t.getId()));
t = service.getOrCreate(TestClass.class, new ObjectId(9090909));
assertTrue(Objects.equals(new ObjectId(9090909), t.getId()));
t = service.getOrCreate(TestClass.class, new Byte("0"));
assertEquals(new Byte("0"), Byte.valueOf(t.getId().toString()));
t = service.getOrCreate(TestClass.class, (byte) 90);
assertEquals((byte) 90, Byte.parseByte(t.getId().toString()));
t = service.getOrCreate(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 {
service.getOrCreate(TestClass.class, new int[]{1, 2, 3, 4, 5});
fail("Should not be here");
} catch (Exception e) {
assertEquals("RId value cannot be an array.", e.getCause().getMessage());
}
try {
service.getOrCreate(TestClass.class, new byte[]{1, 2, 3, 4, 5});
fail("Should not be here");
} 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);
fail("Should not be here");
} 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);
}
@Test
public void testIsPhantom() {
RLiveObjectService service = redisson.getLiveObjectService();
assertFalse(service.isExists(new Object()));
TestClass ts = new TestClass(new ObjectId(100));
assertFalse(service.isExists(service.get(TestClass.class, new ObjectId(100))));
assertFalse(service.isExists(ts));
ts.setValue("VALUE");
ts.setCode("CODE");
TestClass persisted = service.persist(ts);
assertTrue(service.isExists(service.get(TestClass.class, new ObjectId(100))));
assertFalse(service.isExists(ts));
assertTrue(service.isExists(persisted));
}
@Test
public void testIsLiveObject() {
RLiveObjectService service = redisson.getLiveObjectService();
TestClass ts = new TestClass(new ObjectId(100));
assertFalse(service.isLiveObject(ts));
TestClass persisted = service.persist(ts);
assertFalse(service.isLiveObject(ts));
assertTrue(service.isLiveObject(persisted));
}
@Test
public void testAsLiveObject() {
RLiveObjectService service = redisson.getLiveObjectService();
TestClass instance = service.getOrCreate(TestClass.class, new ObjectId(100));
RLiveObject liveObject = service.asLiveObject(instance);
assertEquals(new ObjectId(100), liveObject.getLiveObjectId());
try {
service.asLiveObject(new Object());
fail("Should not be here");
} catch (Exception e) {
assertTrue(e instanceof ClassCastException);
}
}
@Test
public void testClassRegistration() {
RLiveObjectService service = redisson.getLiveObjectService();
service.registerClass(TestClass.class);
assertTrue(service.isClassRegistered(TestClass.class));
RLiveObjectService newService = redisson.getLiveObjectService();
assertTrue(newService.isClassRegistered(TestClass.class));
RedissonClient newRedisson = Redisson.create(redisson.getConfig());
assertFalse(newRedisson.getLiveObjectService().isClassRegistered(TestClass.class));
newRedisson.shutdown(1, 5, TimeUnit.SECONDS);
}
@Test
public void testClassUnRegistration() {
RLiveObjectService service = redisson.getLiveObjectService();
service.registerClass(TestClass.class);
assertTrue(service.isClassRegistered(TestClass.class));
RLiveObjectService newService = redisson.getLiveObjectService();
RedissonClient newRedisson = Redisson.create(redisson.getConfig());
newRedisson.getLiveObjectService().registerClass(TestClass.class);
newService.unregisterClass(TestClass.class);
assertFalse(service.isClassRegistered(TestClass.class));
assertFalse(newService.isClassRegistered(TestClass.class));
assertTrue(newRedisson.getLiveObjectService().isClassRegistered(TestClass.class));
assertFalse(service.isClassRegistered(TestClass.class));
assertFalse(newService.isClassRegistered(TestClass.class));
newRedisson.shutdown(1, 5, TimeUnit.SECONDS);
}
@Test
public void testGet() {
RLiveObjectService service = redisson.getLiveObjectService();
assertNull(service.get(TestClass.class, new ObjectId(100)));
TestClass ts = new TestClass(new ObjectId(100));
TestClass persisted = service.persist(ts);
assertNull(service.get(TestClass.class, new ObjectId(100)));
persisted.setCode("CODE");
assertNotNull(service.get(TestClass.class, new ObjectId(100)));
}
@Test
public void testGetOrCreate() {
RLiveObjectService service = redisson.getLiveObjectService();
assertNotNull(service.getOrCreate(TestClass.class, new ObjectId(100)));
TestClass ts = new TestClass(new ObjectId(100));
TestClass persisted = service.persist(ts);
assertNotNull(service.getOrCreate(TestClass.class, new ObjectId(100)));
persisted.setCode("CODE");
assertNotNull(service.getOrCreate(TestClass.class, new ObjectId(100)));
}
@Test
public void testRemoveByInstance() {
RLiveObjectService service = redisson.getLiveObjectService();
TestClass ts = new TestClass(new ObjectId(100));
ts.setCode("CODE");
TestClass persisted = service.persist(ts);
assertTrue(service.isExists(persisted));
service.delete(persisted);
assertFalse(service.isExists(persisted));
}
@Test
public void testRemoveById() {
RLiveObjectService service = redisson.getLiveObjectService();
TestClass ts = new TestClass(new ObjectId(100));
ts.setCode("CODE");
TestClass persisted = service.persist(ts);
assertTrue(service.isExists(persisted));
service.delete(TestClass.class, new ObjectId(100));
assertFalse(service.isExists(persisted));
}
@REntity
public static class TestClassID1 {
@RId(generator = DistributedAtomicLongIdGenerator.class)
private Long name;
public TestClassID1(Long name) {
this.name = name;
}
public Long getName() {
return name;
}
}
@REntity
public static class TestClassID2 {
@RId(generator = DistributedAtomicLongIdGenerator.class)
private Long name;
public TestClassID2(Long name) {
this.name = name;
}
public Long getName() {
return name;
}
}
@Test
public void testCreate() {
RLiveObjectService service = redisson.getLiveObjectService();
TestClass ts = service.create(TestClass.class);
UUID uuid = UUID.fromString(ts.getId().toString());
assertEquals(4, uuid.version());
TestClassID1 tc1 = service.create(TestClassID1.class);
assertEquals(new Long(1), tc1.getName());
TestClassID2 tc2 = service.create(TestClassID2.class);
assertEquals(new Long(1), tc2.getName());
}
@Test
public void testTransformation() {
RLiveObjectService service = redisson.getLiveObjectService();
TestClass ts = service.create(TestClass.class);
HashMap<String, String> m = new HashMap();
ts.setContent(m);
assertFalse(HashMap.class.isAssignableFrom(ts.getContent().getClass()));
assertTrue(RMap.class.isAssignableFrom(ts.getContent().getClass()));
HashSet<String> s = new HashSet();
ts.setContent(s);
assertFalse(HashSet.class.isAssignableFrom(ts.getContent().getClass()));
assertTrue(RSet.class.isAssignableFrom(ts.getContent().getClass()));
BitSet bs = new BitSet();
ts.setContent(bs);
assertFalse(BitSet.class.isAssignableFrom(ts.getContent().getClass()));
assertTrue(RBitSet.class.isAssignableFrom(ts.getContent().getClass()));
TreeSet<String> ss = new TreeSet();
ts.setContent(ss);
assertFalse(TreeSet.class.isAssignableFrom(ts.getContent().getClass()));
assertTrue(RSortedSet.class.isAssignableFrom(ts.getContent().getClass()));
ArrayList<String> al = new ArrayList();
ts.setContent(al);
assertFalse(ArrayList.class.isAssignableFrom(ts.getContent().getClass()));
assertTrue(RList.class.isAssignableFrom(ts.getContent().getClass()));
ConcurrentHashMap<String, String> chm = new ConcurrentHashMap();
ts.setContent(chm);
assertFalse(ConcurrentHashMap.class.isAssignableFrom(ts.getContent().getClass()));
assertTrue(RMap.class.isAssignableFrom(ts.getContent().getClass()));
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue(10);
ts.setContent(abq);
assertFalse(ArrayBlockingQueue.class.isAssignableFrom(ts.getContent().getClass()));
assertTrue(RBlockingQueue.class.isAssignableFrom(ts.getContent().getClass()));
ConcurrentLinkedQueue<String> clq = new ConcurrentLinkedQueue();
ts.setContent(clq);
assertFalse(ConcurrentLinkedQueue.class.isAssignableFrom(ts.getContent().getClass()));
assertTrue(RQueue.class.isAssignableFrom(ts.getContent().getClass()));
LinkedBlockingDeque<String> lbdq = new LinkedBlockingDeque();
ts.setContent(lbdq);
assertFalse(LinkedBlockingDeque.class.isAssignableFrom(ts.getContent().getClass()));
assertTrue(RBlockingDeque.class.isAssignableFrom(ts.getContent().getClass()));
LinkedList<String> ll = new LinkedList();
ts.setContent(ll);
assertFalse(LinkedList.class.isAssignableFrom(ts.getContent().getClass()));
assertTrue(RDeque.class.isAssignableFrom(ts.getContent().getClass()));
}
@REntity(fieldTransformation = REntity.TransformationMode.IMPLEMENTATION_BASED)
public static class TestClassNoTransformation {
private String value;
private String code;
private Object content;
@RId
private Serializable id;
public TestClassNoTransformation(Serializable id) {
this.id = id;
}
public Serializable getId() {
return id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
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 = 33 * hash + Objects.hashCode(this.value);
hash = 33 * hash + Objects.hashCode(this.code);
hash = 33 * hash + Objects.hashCode(this.id);
hash = 33 * hash + Objects.hashCode(this.content);
return hash;
}
}
@Test
public void testNoTransformation() {
RLiveObjectService service = redisson.getLiveObjectService();
TestClassNoTransformation ts = service.create(TestClassNoTransformation.class);
HashMap<String, String> m = new HashMap();
ts.setContent(m);
assertTrue(HashMap.class.isAssignableFrom(ts.getContent().getClass()));
assertFalse(RMap.class.isAssignableFrom(ts.getContent().getClass()));
HashSet<String> s = new HashSet();
ts.setContent(s);
assertTrue(HashSet.class.isAssignableFrom(ts.getContent().getClass()));
assertFalse(RSet.class.isAssignableFrom(ts.getContent().getClass()));
BitSet bs = new BitSet();
ts.setContent(bs);
assertTrue(BitSet.class.isAssignableFrom(ts.getContent().getClass()));
assertFalse(RBitSet.class.isAssignableFrom(ts.getContent().getClass()));
TreeSet<String> ss = new TreeSet();
ts.setContent(ss);
assertTrue(TreeSet.class.isAssignableFrom(ts.getContent().getClass()));
assertFalse(RSortedSet.class.isAssignableFrom(ts.getContent().getClass()));
ArrayList<String> al = new ArrayList();
ts.setContent(al);
assertTrue(ArrayList.class.isAssignableFrom(ts.getContent().getClass()));
assertFalse(RList.class.isAssignableFrom(ts.getContent().getClass()));
ConcurrentHashMap<String, String> chm = new ConcurrentHashMap();
ts.setContent(chm);
assertTrue(ConcurrentHashMap.class.isAssignableFrom(ts.getContent().getClass()));
assertFalse(RMap.class.isAssignableFrom(ts.getContent().getClass()));
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue(10);
abq.add("111");
ts.setContent(abq);
assertTrue(ArrayBlockingQueue.class.isAssignableFrom(ts.getContent().getClass()));
assertFalse(RBlockingQueue.class.isAssignableFrom(ts.getContent().getClass()));
ConcurrentLinkedQueue<String> clq = new ConcurrentLinkedQueue();
ts.setContent(clq);
assertTrue(ConcurrentLinkedQueue.class.isAssignableFrom(ts.getContent().getClass()));
assertFalse(RQueue.class.isAssignableFrom(ts.getContent().getClass()));
LinkedBlockingDeque<String> lbdq = new LinkedBlockingDeque();
ts.setContent(lbdq);
assertTrue(LinkedBlockingDeque.class.isAssignableFrom(ts.getContent().getClass()));
assertFalse(RBlockingDeque.class.isAssignableFrom(ts.getContent().getClass()));
LinkedList<String> ll = new LinkedList();
ts.setContent(ll);
assertTrue(LinkedList.class.isAssignableFrom(ts.getContent().getClass()));
assertFalse(RDeque.class.isAssignableFrom(ts.getContent().getClass()));
}
}

@ -1,13 +1,11 @@
package org.redisson;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author Rui Gu (https://github.com/jackygurui)
*/
public class RedissonRuntimeEnvironment {
public static final boolean isTravis = "true".equalsIgnoreCase(System.getProperty("travisEnv"));

Loading…
Cancel
Save