/* See LICENSE for licensing and NOTICE for copyright. */ package org.ldaptive.cache; import java.time.Duration; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.ldaptive.SearchRequest; import org.ldaptive.SearchResult; /** * Least-Recently-Used cache implementation. Leverages a {@link LinkedHashMap}. * * @param <Q> type of search request * * @author Middleware Services */ public class LRUCache<Q extends SearchRequest> implements Cache<Q> { /** Initial capacity of the hash map. */ private static final int INITIAL_CAPACITY = 16; /** Load factor of the hash map. */ private static final float LOAD_FACTOR = 0.75f; /** Map to cache search results. */ private Map<Q, Item> cache; /** Executor for performing eviction. */ private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor( // CheckStyle:JavadocVariable OFF r -> { final Thread t = new Thread(r); t.setDaemon(true); return t; }); // CheckStyle:JavadocVariable ON /** * Creates a new LRU cache. * * @param size number of results to cache * @param timeToLive that results should stay in the cache * @param interval to enforce timeToLive */ public LRUCache(final int size, final Duration timeToLive, final Duration interval) { cache = new LinkedHashMap<Q, Item>(INITIAL_CAPACITY, LOAD_FACTOR, true) { /** serialVersionUID. */ private static final long serialVersionUID = -4082551016104288539L; @Override protected boolean removeEldestEntry(final Map.Entry<Q, Item> entry) { return size() > size; } }; final Runnable expire = () -> { synchronized (cache) { final Iterator<Item> i = cache.values().iterator(); final long t = System.currentTimeMillis(); while (i.hasNext()) { final Item item = i.next(); if (t - item.creationTime > timeToLive.toMillis()) { i.remove(); } } } }; executor.scheduleAtFixedRate(expire, interval.toMillis(), interval.toMillis(), TimeUnit.MILLISECONDS); } /** Removes all data from this cache. */ public void clear() { synchronized (cache) { cache.clear(); } } @Override public SearchResult get(final Q request) { synchronized (cache) { if (cache.containsKey(request)) { return cache.get(request).result; } else { return null; } } } @Override public void put(final Q request, final SearchResult result) { synchronized (cache) { cache.put(request, new Item(result)); } } /** * Returns the number of items in this cache. * * @return size of this cache */ public int size() { synchronized (cache) { return cache.size(); } } /** Frees any resources associated with this cache. */ public void close() { executor.shutdown(); cache = null; } /** Container for data related to cached ldap results. */ private class Item { /** Ldap result. */ private final SearchResult result; /** Timestamp when this item is created. */ private final long creationTime; /** * Creates a new item. * * @param sr search result */ Item(final SearchResult sr) { result = sr; creationTime = System.currentTimeMillis(); } } }