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

sirius.kernel.cache.ManagedCache Maven / Gradle / Ivy

Go to download

Provides common core classes and the microkernel powering all Sirius applications

There is a newer version: 12.9.1
Show newest version
/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.kernel.cache;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import sirius.kernel.Sirius;
import sirius.kernel.commons.Callback;
import sirius.kernel.commons.Tuple;
import sirius.kernel.health.Counter;
import sirius.kernel.health.Exceptions;
import sirius.kernel.settings.Extension;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.function.Predicate;

/**
 * Implementation of Cache used by the CacheManager
 *
 * @param  the type of the keys used by this cache
 * @param  the type of the values supported by this cache
 */
class ManagedCache implements Cache, RemovalListener {

    protected static final int MAX_HISTORY = 25;
    private static final double ONE_HUNDERT_PERCENT = 100d;
    protected List usesHistory = new ArrayList<>(MAX_HISTORY);
    protected List hitRateHistory = new ArrayList<>(MAX_HISTORY);

    protected int maxSize;
    protected ValueComputer computer;
    protected com.google.common.cache.Cache> data;
    protected Counter hits = new Counter();
    protected Counter misses = new Counter();
    protected Date lastEvictionRun = null;
    protected final String name;
    protected long timeToLive;
    protected final ValueVerifier verifier;
    protected long verificationInterval;
    protected Callback> removeListener;

    private static final String EXTENSION_TYPE_CACHE = "cache";
    private static final String CONFIG_KEY_MAX_SIZE = "maxSize";
    private static final String CONFIG_KEY_TTL = "ttl";
    private static final String CONFIG_KEY_VERIFICATION = "verification";

    /**
     * Creates a new cache. This is not intended to be called outside of CacheManager.
     *
     * @param name          name of the cache which is also used to fetch the config settings
     * @param valueComputer used to compute absent cache values for given keys. May be null.
     * @param verifier      used to verify cached values before they are delivered to the caller.
     */
    protected ManagedCache(String name,
                           @Nullable ValueComputer valueComputer,
                           @Nullable ValueVerifier verifier) {
        this.name = name;
        this.computer = valueComputer;
        this.verifier = verifier;
    }

    /*
     * Initializes the cache on first use. This is necessary since most of  the caches will be created by the
     * static initializes when their classes are created. At this point, the config has not been loaded yet.
     */
    protected void init() {
        if (data != null) {
            return;
        }

        Extension cacheInfo = Sirius.getSettings().getExtension(EXTENSION_TYPE_CACHE, name);
        if (cacheInfo.isDefault()) {
            CacheManager.LOG.WARN("Cache %s does not exist! Using defaults...", name);
        }
        this.verificationInterval = cacheInfo.getMilliseconds(CONFIG_KEY_VERIFICATION);
        this.timeToLive = cacheInfo.getMilliseconds(CONFIG_KEY_TTL);
        this.maxSize = cacheInfo.get(CONFIG_KEY_MAX_SIZE).getInteger();
        if (maxSize > 0) {
            this.data = CacheBuilder.newBuilder().maximumSize(maxSize).removalListener(this).build();
        } else {
            this.data = CacheBuilder.newBuilder().removalListener(this).build();
        }
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getMaxSize() {
        return maxSize;
    }

    @Override
    public int getSize() {
        if (data == null) {
            return 0;
        }
        return (int) data.size();
    }

    @Override
    public long getUses() {
        return hits.getCount() + misses.getCount();
    }

    @Override
    public Long getHitRate() {
        long h = hits.getCount();
        long m = misses.getCount();
        return h + m == 0L ? 0L : Math.round(ONE_HUNDERT_PERCENT * h / (h + m));
    }

    @Override
    public Date getLastEvictionRun() {
        return (Date) lastEvictionRun.clone();
    }

    protected void runEviction() {
        if (data == null) {
            return;
        }
        if (timeToLive <= 0) {
            return;
        }
        lastEvictionRun = new Date();
        // Remove all outdated entries...
        long now = System.currentTimeMillis();
        int numEvicted = 0;
        Iterator>> iter = data.asMap().entrySet().iterator();
        while (iter.hasNext()) {
            Entry> next = iter.next();
            if (next.getValue().getMaxAge() < now) {
                iter.remove();
                numEvicted++;
            }
        }
        if (numEvicted > 0 && CacheManager.LOG.isFINE()) {
            CacheManager.LOG.FINE("Evicted %d entries from %s", numEvicted, name);
        }
    }

    protected void updateStatistics() {
        usesHistory.add(getUses());
        if (usesHistory.size() > MAX_HISTORY) {
            usesHistory.remove(0);
        }
        hitRateHistory.add(getHitRate());
        if (hitRateHistory.size() > MAX_HISTORY) {
            hitRateHistory.remove(0);
        }
        hits.reset();
        misses.reset();
    }

    @Override
    public void clear() {
        if (data == null) {
            return;
        }
        data.asMap().clear();
        misses.reset();
        hits.reset();
        lastEvictionRun = new Date();
    }

    @Override
    public V get(K key) {
        return get(key, this.computer);
    }

    @Override
    public V get(final K key, final ValueComputer computer) {
        try {
            if (key == null) {
                return null;
            }
            // Caches are lazily initialized so that the system config is present once they are accessed
            if (data == null) {
                init();
            }

            CacheEntry entry = data.getIfPresent(key);

            if (entry != null) {
                long now = System.currentTimeMillis();
                entry = verifyEntry(entry, now);
            }

            if (entry != null) {
                // Entry was found (and verified) - increment statistics
                hits.inc();
                entry.getHits().inc();
            } else {
                // No entry was found, try to compute one if possible
                misses.inc();
                entry = tryComputeEntry(key, computer);
            }

            if (entry != null) {
                return entry.getValue();
            } else {
                return null;
            }
        } catch (Exception e) {
            throw Exceptions.handle(CacheManager.LOG, e);
        }
    }

    private CacheEntry tryComputeEntry(K key, ValueComputer computer) {
        if (computer == null) {
            return null;
        }
        V value = computer.compute(key);
        CacheEntry entry = new CacheEntry<>(key,
                                                  value,
                                                  timeToLive > 0 ? timeToLive + System.currentTimeMillis() : 0,
                                                  verificationInterval + System.currentTimeMillis());
        data.put(key, entry);
        return entry;
    }

    private CacheEntry verifyEntry(CacheEntry entry, long now) {
        // Verify age of entry
        if (entry.getMaxAge() > 0 && entry.getMaxAge() < now) {
            data.invalidate(entry.getKey());
            return null;
        }

        // Apply verifier if present
        if (verifier != null && verificationInterval > 0 && entry.getNextVerification() < now) {
            if (!verifier.valid(entry.getValue())) {
                data.invalidate(entry.getKey());
                return null;
            }
        }
        return entry;
    }

    @Override
    public void put(K key, V value) {
        if (key == null) {
            throw new IllegalArgumentException("key must not be null");
        }
        if (data == null) {
            init();
        }
        CacheEntry cv = new CacheEntry<>(key,
                                               value,
                                               timeToLive > 0 ? timeToLive + System.currentTimeMillis() : 0,
                                               verificationInterval + System.currentTimeMillis());
        data.put(key, cv);
    }

    @Override
    public void remove(K key) {
        if (data == null) {
            return;
        }
        data.invalidate(key);
    }

    @Override
    public Iterator keySet() {
        if (data == null) {
            init();
        }
        return data.asMap().keySet().iterator();
    }

    @Override
    public List> getContents() {
        if (data == null) {
            init();
        }
        return new ArrayList<>(data.asMap().values());
    }

    @Override
    public void removeIf(@Nonnull Predicate> predicate) {
        if (data == null) {
            return;
        }

        data.asMap().values().removeIf(predicate);
    }

    @Override
    public List getUseHistory() {
        return usesHistory;
    }

    @Override
    public List getHitRateHistory() {
        return hitRateHistory;
    }

    @Override
    public Cache onRemove(Callback> onRemoveCallback) {
        removeListener = onRemoveCallback;
        return this;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void onRemoval(RemovalNotification notification) {
        if (removeListener != null) {
            try {
                CacheEntry entry = (CacheEntry) notification.getValue();
                removeListener.invoke(Tuple.create(entry.getKey(), entry.getValue()));
            } catch (Exception e) {
                Exceptions.handle(e);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy