All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.redisson.RedissonLiveObjectService Maven / Gradle / Ivy

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

There is a newer version: 3.40.2
Show newest version
/**
 * Copyright (c) 2013-2024 Nikita Koksharov
 *
 * 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 jodd.bean.BeanCopy;
import jodd.bean.BeanUtil;
import jodd.introspector.ClassIntrospector;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldDescription.InDefinedShape;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.type.TypeDescription;
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.api.*;
import org.redisson.api.annotation.*;
import org.redisson.api.condition.Condition;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.convertor.Convertor;
import org.redisson.client.protocol.decoder.ListMultiDecoder2;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ListScanResultReplayDecoder;
import org.redisson.client.protocol.decoder.ObjectListReplayDecoder;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.command.CommandBatchService;
import org.redisson.liveobject.LiveObjectSearch;
import org.redisson.liveobject.LiveObjectTemplate;
import org.redisson.liveobject.core.AccessorInterceptor;
import org.redisson.liveobject.core.FieldAccessorInterceptor;
import org.redisson.liveobject.core.LiveObjectInterceptor;
import org.redisson.liveobject.core.RMapInterceptor;
import org.redisson.liveobject.misc.AdvBeanCopy;
import org.redisson.liveobject.misc.ClassUtils;
import org.redisson.liveobject.misc.Introspectior;
import org.redisson.liveobject.resolver.MapResolver;
import org.redisson.liveobject.resolver.NamingScheme;
import org.redisson.liveobject.resolver.RIdResolver;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

public class RedissonLiveObjectService implements RLiveObjectService {

    private static final ConcurrentMap>, RIdResolver> PROVIDER_CACHE = new ConcurrentHashMap<>();

    private final ConcurrentMap, Class> classCache;
    private final CommandAsyncExecutor commandExecutor;
    private final LiveObjectSearch seachEngine;
    private final MapResolver mapResolver;

    public RedissonLiveObjectService(ConcurrentMap, Class> classCache,
                                     CommandAsyncExecutor commandExecutor) {
        this.classCache = classCache;
        this.commandExecutor = commandExecutor;
        this.seachEngine = new LiveObjectSearch(commandExecutor);
        this.mapResolver = commandExecutor.getServiceManager().getLiveObjectMapResolver();
    }

    private void addExpireListener(CommandAsyncExecutor commandExecutor) {
        if (commandExecutor.getServiceManager().getLiveObjectLatch().compareAndSet(false, true)) {
            String pp = "__keyspace@" + commandExecutor.getServiceManager().getConfig().getDatabase() + "__:redisson_live_object:*";
            String prefix = pp.replace(":redisson_live_object:*", "");
            RPatternTopic topic = new RedissonPatternTopic(StringCodec.INSTANCE, commandExecutor, pp);
            topic.addListenerAsync(String.class, (pattern, channel, msg) -> {
                if (!msg.equals("expired")) {
                    return;
                }

                String name = channel.toString().replace(prefix, "");
                Class entity = resolveEntity(name);
                if (entity == null) {
                    return;
                }

                NamingScheme scheme = commandExecutor.getObjectBuilder().getNamingScheme(entity);
                Object id = scheme.resolveId(name);
                deleteExpired(id, entity);
            });
        }
    }

    private Class resolveEntity(String name) {
        String className = name.substring(name.lastIndexOf(":")+1);
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            return classCache.keySet()
                    .stream()
                    .filter(c -> c.getName().equals(className))
                    .findAny()
                    .orElse(null);
        }
    }


    //TODO: Add ttl renewal functionality
//    @Override
//    public  T get(Class 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;
//    }

    private RMap getMap(Object proxied) {
        return asLiveObject(proxied).getLiveObjectLiveMap();
    }

    private  Object generateId(Class entityClass, String idFieldName) throws NoSuchFieldException {
        RId annotation = ClassUtils.getDeclaredField(entityClass, idFieldName)
                .getAnnotation(RId.class);
        RIdResolver resolver = getResolver(annotation.generator());
        Object id = resolver.resolve(entityClass, annotation, idFieldName, commandExecutor);
        return id;
    }

    private RIdResolver getResolver(Class> resolverClass) {
        if (!PROVIDER_CACHE.containsKey(resolverClass)) {
            try {
                PROVIDER_CACHE.putIfAbsent(resolverClass, resolverClass.newInstance());
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        }
        return PROVIDER_CACHE.get(resolverClass);
    }

    public  T createLiveObject(Class entityClass, Object id) {
        registerClass(entityClass);
        Class proxyClass = classCache.get(entityClass);
        return (T) instantiateLiveObject(proxyClass, id);
    }

    private  T createLiveObject(Class entityClass, Object id, CommandAsyncExecutor commandExecutor, Map, Class> classCache) {
        Class proxyClass = classCache.get(entityClass);
        if (proxyClass == null) {
            validateClass(entityClass);
            proxyClass = createProxy(entityClass, commandExecutor);
            classCache.put(entityClass, proxyClass);
        }
        return (T) instantiateLiveObject(proxyClass, id);
    }

    @Override
    public  T get(Class entityClass, Object id) {
        addExpireListener(commandExecutor);
        T proxied = createLiveObject(entityClass, id);
        if (asLiveObject(proxied).isExists()) {
            return proxied;
        }
        return null;
    }

    @Override
    public  Collection find(Class entityClass, Condition condition) {
        addExpireListener(commandExecutor);
        Set ids = seachEngine.find(entityClass, condition);

        return ids.stream()
                .map(id -> createLiveObject(entityClass, id))
                .collect(Collectors.toList());
    }

    @Override
    public long count(Class entityClass, Condition condition) {
        addExpireListener(commandExecutor);
        Set ids = seachEngine.find(entityClass, condition);
        return ids.size();
    }

    @Override
    public  T attach(T detachedObject) {
        addExpireListener(commandExecutor);
        validateDetached(detachedObject);
        Class entityClass = (Class) detachedObject.getClass();
        String idFieldName = getRIdFieldName(detachedObject.getClass());
        Object id = ClassUtils.getField(detachedObject, idFieldName);
        return createLiveObject(entityClass, id);
    }

    private  T attach(T detachedObject, CommandAsyncExecutor commandExecutor, Map, Class> classCache) {
        validateDetached(detachedObject);
        Class entityClass = (Class) detachedObject.getClass();
        String idFieldName = getRIdFieldName(detachedObject.getClass());
        Object id = ClassUtils.getField(detachedObject, idFieldName);
        return createLiveObject(entityClass, id, commandExecutor, classCache);
    }

    @Override
    public  T merge(T detachedObject) {
        Map alreadyPersisted = new HashMap();
        return persist(detachedObject, alreadyPersisted, RCascadeType.MERGE);
    }

    @Override
    public  T persist(T detachedObject) {
        Map alreadyPersisted = new HashMap();
        return persist(detachedObject, alreadyPersisted, RCascadeType.PERSIST);
    }

    @Override
    public  List persist(T... detachedObjects) {
        return persist(RCascadeType.PERSIST, detachedObjects);
    }

    public  List merge(T... detachedObjects) {
        return persist(RCascadeType.MERGE, detachedObjects);
    }

    public  List persist(RCascadeType type, T... detachedObjects) {
        addExpireListener(commandExecutor);
        CommandBatchService batchService = new CommandBatchService(commandExecutor);

        Map, Class> classCache = new HashMap<>();
        Map detached2Attached = new LinkedHashMap<>();
        Map name2id = new HashMap<>();

        for (T detachedObject : detachedObjects) {
            Object id = getId(detachedObject);

            T attachedObject = attach(detachedObject, batchService, classCache);
            RMap liveMap = getMap(attachedObject);

            detached2Attached.put(detachedObject, attachedObject);
            name2id.put(liveMap.getName(), id);
        }

        if (type == RCascadeType.PERSIST) {
            CommandBatchService checkExecutor = new CommandBatchService(commandExecutor);
            for (Entry entry : name2id.entrySet()) {
                RMap map = new RedissonMap<>(checkExecutor, entry.getKey(), null, null, null);
                map.containsKeyAsync("redisson_live_object");
            }

            BatchResult checkResponse = checkExecutor.execute();
            for (int i = 0; i < checkResponse.getResponses().size(); i++) {
                Boolean value = (Boolean) checkResponse.getResponses().get(i);
                if (value) {
                    List list = new ArrayList<>(name2id.values());
                    Object id = list.get(i);
                    throw new IllegalArgumentException("Object with id=" + id + " already exists.");
                }
            }
        }

        for (Entry entry : detached2Attached.entrySet()) {
            T detachedObject = entry.getKey();
            Object attachedObject = entry.getValue();

            for (FieldDescription.InDefinedShape field : Introspectior.getAllFields(detachedObject.getClass())) {
                Object object = ClassUtils.getField(detachedObject, field.getName());
                if (object == null
                        || object instanceof Collection
                        || ClassUtils.isAnnotationPresent(object.getClass(), REntity.class)) {
                    continue;
                }

                validateAnnotation(detachedObject, field.getName());
            }

            String idFieldName = getRIdFieldName(detachedObject.getClass());
            copy(detachedObject, attachedObject, Arrays.asList(idFieldName));
        }

        ClassIntrospector.get().reset();

        batchService.execute();

        detached2Attached.values().forEach(attachedObject -> {
            RLiveObject lo = asLiveObject(attachedObject);
            mapResolver.remove(attachedObject.getClass().getSuperclass(), lo.getLiveObjectId());
        });
        classCache.clear();

        List attachedObjects = new ArrayList<>();
        for (Object attachedObject : detached2Attached.values()) {
            RLiveObject lo = asLiveObject(attachedObject);
            T nlo = (T) createLiveObject(attachedObject.getClass().getSuperclass(), lo.getLiveObjectId(), commandExecutor, classCache);
            attachedObjects.add(nlo);
        }
        return attachedObjects;
    }

    private  Object getId(T detachedObject) {
        String idFieldName = getRIdFieldName(detachedObject.getClass());
        Object id = ClassUtils.getField(detachedObject, idFieldName);
        if (id == null) {
            try {
                id = generateId(detachedObject.getClass(), idFieldName);
            } catch (NoSuchFieldException e) {
                throw new IllegalArgumentException(e);
            }
            ClassUtils.setField(detachedObject, idFieldName, id);
        }
        return id;
    }


    private  T persist(T detachedObject, Map alreadyPersisted, RCascadeType type) {
        addExpireListener(commandExecutor);
        validateDetached(detachedObject);
        Object id = getId(detachedObject);

        T attachedObject = attach(detachedObject);
        alreadyPersisted.put(detachedObject, attachedObject);
        RMap liveMap = getMap(attachedObject);

        List excludedFields = new ArrayList<>();
        String idFieldName = getRIdFieldName(detachedObject.getClass());
        excludedFields.add(idFieldName);
        boolean fastResult = liveMap.fastPut("redisson_live_object", "1");
        if (type == RCascadeType.PERSIST && !fastResult) {
            throw new IllegalArgumentException("This REntity already exists.");
        }

        for (FieldDescription.InDefinedShape field : Introspectior.getAllFields(detachedObject.getClass())) {
            Object object = ClassUtils.getField(detachedObject, field.getName());
            if (object == null) {
                continue;
            }

            RObject rObject = commandExecutor.getObjectBuilder().createObject(id, detachedObject.getClass(), object.getClass(), field.getName());
            if (rObject != null) {
                commandExecutor.getObjectBuilder().store(rObject, field.getName(), liveMap);
                if (rObject instanceof SortedSet) {
                    ((RSortedSet) rObject).trySetComparator(((SortedSet) object).comparator());
                }

                if (rObject instanceof Collection) {
                    Collection coll = (Collection) rObject;
                    if (type == RCascadeType.MERGE) {
                        coll.clear();
                    }

                    for (Object obj : (Collection) object) {
                        if (obj != null && ClassUtils.isAnnotationPresent(obj.getClass(), REntity.class)) {
                            Object persisted = alreadyPersisted.get(obj);
                            if (persisted == null
                                    && checkCascade(detachedObject, type, field.getName())) {
                                persisted = persist(obj, alreadyPersisted, type);
                            }
                            obj = persisted;
                        }
                        coll.add(obj);
                    }
                } else if (rObject instanceof Map) {
                    Map rMap = (Map) rObject;
                    if (type == RCascadeType.MERGE) {
                        rMap.clear();
                    }

                    Map map = (Map) object;
                    for (Entry entry : map.entrySet()) {
                        Object key = entry.getKey();
                        Object value = entry.getValue();

                        if (key != null && ClassUtils.isAnnotationPresent(key.getClass(), REntity.class)) {
                            Object persisted = alreadyPersisted.get(key);
                            if (persisted == null
                                    && checkCascade(detachedObject, type, field.getName())) {
                                persisted = persist(key, alreadyPersisted, type);
                            }
                            key = persisted;
                        }

                        if (value != null && ClassUtils.isAnnotationPresent(value.getClass(), REntity.class)) {
                            Object persisted = alreadyPersisted.get(value);
                            if (persisted == null
                                    && checkCascade(detachedObject, type, field.getName())) {
                                persisted = persist(value, alreadyPersisted, type);
                            }
                            value = persisted;
                        }

                        rMap.put(key, value);
                    }
                }
                excludedFields.add(field.getName());
            } else if (ClassUtils.isAnnotationPresent(object.getClass(), REntity.class)) {
                Object persisted = alreadyPersisted.get(object);
                if (persisted == null) {
                    if (checkCascade(detachedObject, type, field.getName())) {
                        persisted = persist(object, alreadyPersisted, type);
                    }
                }

                excludedFields.add(field.getName());
                BeanUtil.pojo.setSimpleProperty(attachedObject, field.getName(), persisted);
            } else {
                validateAnnotation(detachedObject, field.getName());
            }

        }
        copy(detachedObject, attachedObject, excludedFields);
        return attachedObject;
    }

    private void validateAnnotation(Object instance, String fieldName) {
        Class clazz = instance.getClass();
        if (isLiveObject(instance)) {
            clazz = clazz.getSuperclass();
        }

        RCascade annotation = ClassUtils.getAnnotation(clazz, fieldName, RCascade.class);
        if (annotation != null) {
            throw new IllegalArgumentException("RCascade annotation couldn't be defined for non-Redisson object '" + clazz + "' and field '" + fieldName + "'");
        }
    }

    private  boolean checkCascade(Object instance, RCascadeType type, String fieldName) {
        Class clazz = instance.getClass();
        if (isLiveObject(instance)) {
            clazz = clazz.getSuperclass();
        }

        RCascade annotation = ClassUtils.getAnnotation(clazz, fieldName, RCascade.class);
        if (annotation != null && (Arrays.asList(annotation.value()).contains(type)
                || Arrays.asList(annotation.value()).contains(RCascadeType.ALL))) {
            return true;
        }
        return false;
    }

    @Override
    public  T detach(T attachedObject) {
        addExpireListener(commandExecutor);
        Map alreadyDetached = new HashMap();
        return detach(attachedObject, alreadyDetached);
    }

    @SuppressWarnings("unchecked")
    private  T detach(T attachedObject, Map alreadyDetached) {
        validateAttached(attachedObject);
        T detached = instantiateDetachedObject((Class) attachedObject.getClass().getSuperclass(), asLiveObject(attachedObject).getLiveObjectId());
        new BeanCopy(attachedObject, detached).declared(true).copy();
        alreadyDetached.put(getMap(attachedObject).getName(), detached);

        for (Entry obj : getMap(attachedObject).entrySet()) {
            if (!checkCascade(attachedObject, RCascadeType.DETACH, obj.getKey())) {
                continue;
            }

            if (obj.getValue() instanceof RSortedSet) {
                SortedSet redissonSet = (SortedSet) obj.getValue();
                Set set = new TreeSet(redissonSet.comparator());
                for (Object object : redissonSet) {
                    if (isLiveObject(object)) {
                        Object detachedObject = alreadyDetached.get(getMap(object).getName());
                        if (detachedObject == null) {
                            detachedObject = detach(object, alreadyDetached);
                        }
                        object = detachedObject;
                    }
                    set.add(object);
                }

                ClassUtils.setField(detached, obj.getKey(), set);
            } else if (obj.getValue() instanceof RDeque) {
                Collection redissonDeque = (Collection) obj.getValue();
                Deque deque = new LinkedList();
                for (Object object : redissonDeque) {
                    if (isLiveObject(object)) {
                        Object detachedObject = alreadyDetached.get(getMap(object).getName());
                        if (detachedObject == null) {
                            detachedObject = detach(object, alreadyDetached);
                        }
                        object = detachedObject;
                    }
                    deque.add(object);
                }

                ClassUtils.setField(detached, obj.getKey(), deque);
            } else if (obj.getValue() instanceof RQueue) {
                Collection redissonQueue = (Collection) obj.getValue();
                Queue queue = new LinkedList();
                for (Object object : redissonQueue) {
                    if (isLiveObject(object)) {
                        Object detachedObject = alreadyDetached.get(getMap(object).getName());
                        if (detachedObject == null) {
                            detachedObject = detach(object, alreadyDetached);
                        }
                        object = detachedObject;
                    }
                    queue.add(object);
                }

                ClassUtils.setField(detached, obj.getKey(), queue);
            } else if (obj.getValue() instanceof RSet) {
                Set set = new HashSet();
                Collection redissonSet = (Collection) obj.getValue();
                for (Object object : redissonSet) {
                    if (isLiveObject(object)) {
                        Object detachedObject = alreadyDetached.get(getMap(object).getName());
                        if (detachedObject == null) {
                            detachedObject = detach(object, alreadyDetached);
                        }
                        object = detachedObject;
                    }
                    set.add(object);
                }

                ClassUtils.setField(detached, obj.getKey(), set);
            } else if (obj.getValue() instanceof RList) {
                List list = new ArrayList();
                Collection redissonList = (Collection) obj.getValue();
                for (Object object : redissonList) {
                    if (isLiveObject(object)) {
                        Object detachedObject = alreadyDetached.get(getMap(object).getName());
                        if (detachedObject == null) {
                            detachedObject = detach(object, alreadyDetached);
                        }
                        object = detachedObject;
                    }
                    list.add(object);
                }

                ClassUtils.setField(detached, obj.getKey(), list);
            } else if (isLiveObject(obj.getValue())) {
                Object detachedObject = alreadyDetached.get(getMap(obj.getValue()).getName());
                if (detachedObject == null) {
                    detachedObject = detach(obj.getValue(), alreadyDetached);
                }
                ClassUtils.setField(detached, obj.getKey(), detachedObject);
            } else if (obj.getValue() instanceof RMap) {
                Map map = new LinkedHashMap();
                Map redissonMap = (Map) obj.getValue();
                for (Entry entry : redissonMap.entrySet()) {
                    Object key = entry.getKey();
                    Object value = entry.getValue();

                    if (isLiveObject(key)) {
                        Object detachedObject = alreadyDetached.get(getMap(key).getName());
                        if (detachedObject == null) {
                            detachedObject = detach(key, alreadyDetached);
                        }
                        key = detachedObject;
                    }

                    if (isLiveObject(value)) {
                        Object detachedObject = alreadyDetached.get(getMap(value).getName());
                        if (detachedObject == null) {
                            detachedObject = detach(value, alreadyDetached);
                        }
                        value = detachedObject;
                    }
                    map.put(key, value);
                }

                ClassUtils.setField(detached, obj.getKey(), map);
            } else {
                validateAnnotation(detached, obj.getKey());
            }
        }

        return detached;
    }

    @Override
    public  void delete(T attachedObject) {
        addExpireListener(commandExecutor);
        Set deleted = new HashSet();
        delete(attachedObject, deleted);
    }

    private  void delete(T attachedObject, Set deleted) {
        validateAttached(attachedObject);

        for (Entry obj : getMap(attachedObject).entrySet()) {
            if (!checkCascade(attachedObject, RCascadeType.DELETE, obj.getKey())) {
                continue;
            }

            if (obj.getValue() instanceof RSortedSet) {
                deleteCollection(deleted, (Iterable) obj.getValue());
                ((RObject) obj.getValue()).delete();
            } else if (obj.getValue() instanceof RDeque) {
                deleteCollection(deleted, (Iterable) obj.getValue());
                ((RObject) obj.getValue()).delete();
            } else if (obj.getValue() instanceof RQueue) {
                deleteCollection(deleted, (Iterable) obj.getValue());
                ((RObject) obj.getValue()).delete();
            } else if (obj.getValue() instanceof RSet) {
                deleteCollection(deleted, (Iterable) obj.getValue());
                ((RObject) obj.getValue()).delete();
            } else if (obj.getValue() instanceof RList) {
                deleteCollection(deleted, (Iterable) obj.getValue());
                ((RObject) obj.getValue()).delete();
            } else if (isLiveObject(obj.getValue())) {
                if (deleted.add(((RedissonObject) getMap(obj.getValue())).getRawName())) {
                    delete(obj.getValue(), deleted);
                }
            } else if (obj.getValue() instanceof RMap) {
                RMap map = (RMap) obj.getValue();
                deleteCollection(deleted, map.keySet());
                deleteCollection(deleted, map.values());
                ((RObject) obj.getValue()).delete();
            } else {
                validateAnnotation(attachedObject, obj.getKey());
            }

        }
        asLiveObject(attachedObject).delete();
    }

    private void deleteCollection(Set deleted, Iterable objs) {
        for (Object object : objs) {
            if (isLiveObject(object)) {
                if (deleted.add(((RedissonObject) getMap(object)).getRawName())) {
                    delete(object, deleted);
                }
            }
        }
    }

    @Override
    public  long delete(Class entityClass, Object... ids) {
        addExpireListener(commandExecutor);
        CommandBatchService ce = new CommandBatchService(commandExecutor);
        FieldList fields = Introspectior.getFieldsWithAnnotation(entityClass, RIndex.class);
        Set fieldNames = fields.stream().map(f -> f.getName()).collect(Collectors.toSet());

        NamingScheme namingScheme = commandExecutor.getObjectBuilder().getNamingScheme(entityClass);
        for (Object id: ids) {
            delete(id, entityClass, namingScheme, ce, fieldNames);
        }

        BatchResult r = (BatchResult) ce.execute();
        return r.getResponses().stream()
                .filter(s -> s instanceof Long)
                .mapToLong(s -> (Long) s).sum();
    }

    private RFuture delete(Object id, Class entityClass, NamingScheme namingScheme, CommandBatchService ce, Set fieldNames) {
        String mapName = namingScheme.getName(entityClass, id);
        Object liveObjectId = namingScheme.resolveId(mapName);

        deleteCollections(id, entityClass, ce);

        RMap liveMap = new RedissonMap<>(namingScheme.getCodec(), commandExecutor,
                mapName, null, null, null);
        Map values = liveMap.getAll(fieldNames);
        for (String fieldName : fieldNames) {
            Object value = values.get(fieldName);
            if (value == null) {
                continue;
            }

            String indexName = namingScheme.getIndexName(entityClass, fieldName);
            if (value instanceof Number) {
                RScoredSortedSetAsync set = new RedissonScoredSortedSet<>(namingScheme.getCodec(), ce, indexName, null);
                set.removeAsync(liveObjectId);
            } else {
                RMultimapAsync idsMultimap = new RedissonSetMultimap<>(namingScheme.getCodec(), ce, indexName);
                idsMultimap.removeAsync(value, liveObjectId);
            }
        }
        mapResolver.remove(entityClass, id);
        return new RedissonKeys(ce).deleteAsync(mapName);
    }

    private void deleteExpired(Object id, Class entityClass) {
        CommandBatchService ce = new CommandBatchService(commandExecutor);
        FieldList fields = Introspectior.getFieldsWithAnnotation(entityClass, RIndex.class);

        deleteCollections(id, entityClass, ce);

        NamingScheme namingScheme = commandExecutor.getObjectBuilder().getNamingScheme(entityClass);
        String mapName = namingScheme.getName(entityClass, id);
        Object liveObjectId = namingScheme.resolveId(mapName);
        TypeDescription.Generic n1 = TypeDescription.Generic.Builder.rawType(Number.class).build();
        for (InDefinedShape field : fields) {
            boolean isNumber = n1.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
                    .isAssignableFrom(field.getType().asErasure().asBoxed().asGenericType());
            String indexName = namingScheme.getIndexName(entityClass, field.getName());
            if (isNumber) {
                RScoredSortedSetAsync set = new RedissonScoredSortedSet<>(namingScheme.getCodec(), ce, indexName, null);
                set.removeAsync(liveObjectId);
            } else {
                RMultimapAsync idsMultimap = new RedissonSetMultimap<>(namingScheme.getCodec(), ce, indexName);
                idsMultimap.fastRemoveValueAsync(liveObjectId);
            }
        }
        ce.execute();
    }

    private void deleteCollections(Object id, Class entityClass, CommandBatchService ce) {
        for (InDefinedShape field : Introspectior.getAllFields(entityClass)) {
            try {
                Field f = ClassUtils.getDeclaredField(entityClass, field.getName());
                RObject rObject = commandExecutor.getObjectBuilder().createObject(id, entityClass, f.getType(), field.getName());
                if (rObject != null) {
                    RedissonObject ro = (RedissonObject) rObject;
                    ce.writeAsync(ro.getRawName(), RedisCommands.DEL, ro.getRawName());
                }
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public RFuture delete(Object id, Class entityClass, NamingScheme namingScheme, CommandBatchService ce) {
        FieldList fields = Introspectior.getFieldsWithAnnotation(entityClass, RIndex.class);
        Set fieldNames = fields.stream().map(f -> f.getName()).collect(Collectors.toSet());
        return delete(id, entityClass, namingScheme, ce, fieldNames);
    }

    @Override
    public  Iterable findIds(Class entityClass) {
        return findIds(entityClass, 10);
    }

    @Override
    public  Iterable findIds(Class entityClass, int count) {
        addExpireListener(commandExecutor);
        NamingScheme namingScheme = commandExecutor.getObjectBuilder().getNamingScheme(entityClass);
        String pattern = namingScheme.getNamePattern(entityClass);
        RedissonKeys keys = new RedissonKeys(commandExecutor);

        RedisCommand> command = new RedisCommand<>("SCAN",
                new ListMultiDecoder2(new ListScanResultReplayDecoder(), new ObjectListReplayDecoder()), new Convertor() {
            int index;
            @Override
            public Object convert(Object obj) {
                index++;
                if (index == 1) {
                    return obj;
                }
                return namingScheme.resolveId(obj.toString());
            }
        });

        return keys.getKeysByPattern(command, pattern, 0, count, null);
    }

    @Override
    public  RLiveObject asLiveObject(T instance) {
        return (RLiveObject) instance;
    }

    @Override
    public  RMap asRMap(T instance) {
        return (RMap) instance;
    }

    @Override
    public  boolean isLiveObject(T instance) {
        return instance instanceof RLiveObject;
    }

    @Override
    public  boolean isExists(T instance) {
        addExpireListener(commandExecutor);
        return instance instanceof RLiveObject && asLiveObject(instance).isExists();
    }

    @Override
    public void registerClass(Class cls) {
        if (!classCache.containsKey(cls)) {
            validateClass(cls);
            Class proxyClass = createProxy(cls, commandExecutor);
            classCache.putIfAbsent(cls, proxyClass);
        }
    }

    @Override
    public void unregisterClass(Class cls) {
        if (cls.isAssignableFrom(RLiveObject.class)) {
            classCache.remove(cls.getSuperclass());
            mapResolver.remove(cls.getSuperclass());
        } else {
            classCache.remove(cls);
            mapResolver.remove(cls);
        }
    }

    @Override
    public boolean isClassRegistered(Class cls) {
        return classCache.containsKey(cls) || classCache.containsValue(cls);
    }

    private  void copy(T detachedObject, T attachedObject, List excludedFields) {
        new AdvBeanCopy(detachedObject, attachedObject)
                .copy(excludedFields);
    }

    private String getRIdFieldName(Class cls) {
        return Introspectior.getREntityIdFieldName(cls);
    }

    private  T instantiateLiveObject(Class proxyClass, Object id) {
        if (id == null) {
            throw new IllegalStateException("Non-null value is required for the field with RId annotation.");
        }
        T instance = instantiate(proxyClass);
        asLiveObject(instance).setLiveObjectId(id);
        return instance;
    }

    private  T instantiateDetachedObject(Class cls, K id) {
        T instance = instantiate(cls);
        String fieldName = getRIdFieldName(cls);
        if (ClassUtils.getField(instance, fieldName) == null) {
            ClassUtils.setField(instance, fieldName, id);
        }
        return instance;
    }

    private  T instantiate(Class cls) {
        try {
            for (Constructor constructor : cls.getDeclaredConstructors()) {
                if (constructor.getParameterTypes().length == 0) {
                    constructor.setAccessible(true);
                    return (T) constructor.newInstance();
                }
            }
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
        throw new IllegalArgumentException("Can't find default constructor for " + cls);
    }

    private  void validateClass(Class entityClass) {
        if (entityClass.isAnonymousClass() || entityClass.isLocalClass()) {
            throw new IllegalArgumentException(entityClass.getName() + " is not publically accessable.");
        }
        if (!ClassUtils.isAnnotationPresent(entityClass, REntity.class)) {
            throw new IllegalArgumentException("REntity annotation is missing from class type declaration.");
        }

        FieldList fields = Introspectior.getFieldsWithAnnotation(entityClass, RIndex.class);
        fields = fields.filter(ElementMatchers.fieldType(ElementMatchers.hasSuperType(
                ElementMatchers.anyOf(Map.class, Collection.class, RObject.class))));
        for (InDefinedShape field : fields) {
            throw new IllegalArgumentException("RIndex annotation couldn't be defined for field '" + field.getName() + "' with type '" + field.getType() + "'");
        }

        FieldList 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 = ClassUtils.getDeclaredField(entityClass, idFieldName);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        if (ClassUtils.isAnnotationPresent(idField.getType(), 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  void validateDetached(T detachedObject) {
        if (detachedObject instanceof RLiveObject) {
            throw new IllegalArgumentException("The object supplied is already a RLiveObject");
        }
    }

    private  void validateAttached(T attachedObject) {
        if (!(attachedObject instanceof RLiveObject)) {
            throw new IllegalArgumentException("The object supplied is must be a RLiveObject");
        }
    }

    private  Class createProxy(Class entityClass, CommandAsyncExecutor commandExecutor) {
        DynamicType.Builder builder = new ByteBuddy()
                .subclass(entityClass);
        for (FieldDescription.InDefinedShape field
                : Introspectior.getTypeDescription(LiveObjectTemplate.class)
                .getDeclaredFields()) {
            builder = builder.define(field);
        }

        Class proxied = builder.method(ElementMatchers.isDeclaredBy(
                                ElementMatchers.anyOf(RLiveObject.class, RExpirable.class, RObject.class))
                        .and(ElementMatchers.isGetter().or(ElementMatchers.isSetter())
                                .or(ElementMatchers.named("isPhantom"))
                                .or(ElementMatchers.named("delete"))))
                .intercept(MethodDelegation.withDefaultConfiguration()
                        .withBinders(FieldProxy.Binder
                                .install(LiveObjectInterceptor.Getter.class,
                                        LiveObjectInterceptor.Setter.class))
                        .to(new LiveObjectInterceptor(commandExecutor, this, entityClass, mapResolver)))
                .implement(RLiveObject.class)

                .method(ElementMatchers.isAnnotatedWith(RFieldAccessor.class)
                        .and(ElementMatchers.named("get")
                                .or(ElementMatchers.named("set"))))
                .intercept(MethodDelegation.to(FieldAccessorInterceptor.class))

                .method(ElementMatchers.isDeclaredBy(Map.class)
                        .or(ElementMatchers.isDeclaredBy(RExpirable.class))
                        .or(ElementMatchers.isDeclaredBy(RExpirableAsync.class))
                        .or(ElementMatchers.isDeclaredBy(ConcurrentMap.class))
                        .or(ElementMatchers.isDeclaredBy(RMapAsync.class))
                        .or(ElementMatchers.isDeclaredBy(RMap.class)))
                .intercept(MethodDelegation.withDefaultConfiguration()
                        .withBinders(FieldProxy.Binder
                                .install(LiveObjectInterceptor.Getter.class,
                                        LiveObjectInterceptor.Setter.class)).to(new RMapInterceptor(commandExecutor, entityClass, mapResolver)))
                .implement(RMap.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.not(ElementMatchers.isDeclaredBy(ConcurrentMap.class)))
                        .and(ElementMatchers.not(ElementMatchers.isDeclaredBy(Map.class)))
                        .and(ElementMatchers.isGetter()
                                .or(ElementMatchers.isSetter()))
                        .and(ElementMatchers.isPublic()
                                .or(ElementMatchers.isProtected()))
                )
                .intercept(MethodDelegation.withDefaultConfiguration()
                        .withBinders(FieldProxy.Binder
                                .install(LiveObjectInterceptor.Getter.class,
                                        LiveObjectInterceptor.Setter.class))
                        .to(new AccessorInterceptor(entityClass, commandExecutor, mapResolver)))

                .make().load(entityClass.getClassLoader(),
                        ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();
        return proxied;
    }
}