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

org.babyfish.jimmer.sql.cache.chain.ChainCacheImpl Maven / Gradle / Ivy

The newest version!
package org.babyfish.jimmer.sql.cache.chain;

import org.babyfish.jimmer.meta.ImmutableProp;
import org.babyfish.jimmer.meta.ImmutableType;
import org.babyfish.jimmer.sql.cache.Cache;
import org.babyfish.jimmer.sql.cache.CacheEnvironment;
import org.babyfish.jimmer.sql.cache.CacheLoader;
import org.babyfish.jimmer.sql.exception.ExecutionException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.function.Supplier;

class ChainCacheImpl implements Cache {

    private static final ThreadLocal> LOADER_LOCAL =
        new ThreadLocal<>();

    protected final ImmutableType type;

    protected final ImmutableProp prop;

    protected final Node node;

    @SuppressWarnings("unchecked")
    public ChainCacheImpl(List> binders) {
        if (binders.isEmpty()) {
            throw new IllegalArgumentException("binders cannot be empty");
        }
        ImmutableType cacheType = null;
        ImmutableProp cacheProp = null;
        Node node = this.createTailNode();
        ListIterator> itr = binders.listIterator(binders.size());
        while (itr.hasPrevious()) {
            Binder binder = itr.previous();
            ImmutableType type = binder.type();
            ImmutableProp prop = binder.prop();
            if (cacheType == null) {
                cacheType = type;
                cacheProp = prop;
            } else {
                if (cacheType != type || cacheProp != prop) {
                    throw new IllegalArgumentException(
                            "Not all binders belong to same type/prop"
                    );
                }
            }
            node = createNode(binder, node);
        }
        this.type = cacheType;
        this.prop = cacheProp;
        this.node = node;
    }

    @Override
    public @NotNull ImmutableType type() {
        return type;
    }

    @Override
    public @Nullable ImmutableProp prop() {
        return prop;
    }

    @NotNull
    @Override
    public Map getAll(@NotNull Collection keys, @NotNull CacheEnvironment env) {
        return usingCacheLoading(env.getLoader(), () -> node.loadAll(keys));
    }

    @Override
    public void deleteAll(@NotNull Collection keys, Object reason) {
        node.deleteAll(keys, reason);
    }

    @SuppressWarnings("unchecked")
    protected Node createNode(Object binder, Node next) {
        if (binder instanceof LoadingBinder) {
            return new LoadingNode<>((LoadingBinder) binder, next);
        }
        return new SimpleNode<>((SimpleBinder) binder, next);
    }

    protected TailNode createTailNode() {
        return new TailNode<>();
    }

    protected interface Node extends CacheChain {
        void deleteAll(@NotNull Collection keys, Object reason);
    }

    private static class LoadingNode implements Node {

        private final LoadingBinder binder;

        private final Node next;

        LoadingNode(LoadingBinder binder, Node next) {
            this.binder = binder;
            this.next = next;
            binder.initialize(next);
        }

        @NotNull
        @Override
        public Map loadAll(@NotNull Collection keys) {
            return binder.getAll(keys);
        }

        @Override
        public void deleteAll(@NotNull Collection keys, Object reason) {
            next.deleteAll(keys, reason);
            binder.deleteAll(keys, reason);
        }
    }

    protected static class SimpleNode implements Node {

        protected final SimpleBinder binder;

        protected final Node next;

        protected SimpleNode(SimpleBinder binder, Node next) {
            this.binder = binder;
            this.next = next;
        }

        @NotNull
        @Override
        public Map loadAll(@NotNull Collection keys) {
            Map map = binder.getAll(keys);
            if (map.size() < keys.size()) {
                if (binder instanceof LockedBinder) {
                    LockedBinder lockedBinder = (LockedBinder) binder;
                    Set missedKeys = missedKeys(keys, map);
                    try {
                        lockedBinder.locker().locking(
                                lockedBinder.unwrap(),
                                missedKeys,
                                lockedBinder.waitDuration(),
                                lockedBinder.leaseDuration(),
                                locked -> {
                                    loadAllForNext(missedKeys, map, locked);
                                }
                        );
                    } catch (ExecutionException ex) {
                        throw ex;
                    } catch (Exception ex) {
                        throw new ExecutionException(
                                "Failed to load missed data and update cache",
                                ex
                        );
                    }
                } else {
                    loadAllForNext(missedKeys(keys, map), map, true);
                }
            }
            return map;
        }

        @Override
        public void deleteAll(@NotNull Collection keys, Object reason) {
            if (keys.isEmpty()) {
                return;
            }
            if (binder instanceof LockedBinder) {
                LockedBinder lockedBinder = (LockedBinder) binder;
                try {
                    lockedBinder.locker().locking(
                            lockedBinder.unwrap(),
                            keys instanceof Set ?
                                    (Set) keys :
                                    new LinkedHashSet<>(keys),
                            null,
                            lockedBinder.leaseDuration(),
                            locked -> {
                                next.deleteAll(keys, reason);
                                binder.deleteAll(keys, reason);
                            }
                    );
                } catch (ExecutionException ex) {
                    throw ex;
                } catch (Exception ex) {
                    throw new ExecutionException(
                            "Failed to delete keys from cache",
                            ex
                    );
                }
            } else {
                next.deleteAll(keys, reason);
                binder.deleteAll(keys, reason);
            }
        }

        private static  Set missedKeys(Collection keys, Map loadedMap) {
            Set missedKeys = new LinkedHashSet<>();
            for (K key : keys) {
                if (!loadedMap.containsKey(key)) {
                    missedKeys.add(key);
                }
            }
            return missedKeys;
        }

        private void loadAllForNext(
                Collection missedKeys,
                Map loadedMap,
                boolean updateBinder
        ) {
            Map mapFromNext = next.loadAll(missedKeys);
            if (mapFromNext.size() < missedKeys.size()) {
                mapFromNext = new HashMap<>(mapFromNext);
                if (updateBinder) {
                    for (K missedKey : missedKeys) {
                        if (!mapFromNext.containsKey(missedKey)) {
                            mapFromNext.put(missedKey, null);
                        }
                    }
                }
            }
            if (updateBinder) {
                binder.setAll(mapFromNext);
            }
            loadedMap.putAll(mapFromNext);
        }
    }

    protected static class TailNode implements Node {

        @NotNull
        @Override
        public Map loadAll(@NotNull Collection keys) {
            CacheLoader loader = currentCacheLoader();
            return loader.loadAll(keys);
        }

        @Override
        public void deleteAll(@NotNull Collection keys, Object reason) {
        }
    }

    protected static  R usingCacheLoading(
            CacheLoader loader,
            Supplier block
    ) {
        if (loader == null) {
            throw new IllegalArgumentException("loader cannot be null");
        }
        CacheLoader oldLoader = LOADER_LOCAL.get();
        LOADER_LOCAL.set(loader);
        try {
            return block.get();
        } finally {
            if (oldLoader != null) {
                LOADER_LOCAL.set(oldLoader);
            } else {
                LOADER_LOCAL.remove();
            }
        }
    }

    @SuppressWarnings("unchecked")
    protected static  CacheLoader currentCacheLoader() {
        CacheLoader loader = LOADER_LOCAL.get();
        if (loader == null) {
            throw new IllegalStateException(
                    "Cache binder can only be called by chain cache"
            );
        }
        return (CacheLoader) loader;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy