commit
fdaaf99cfe
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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()));
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue