MultiCache.java

157 lines | 5.096 kB Blame History Raw Download
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package br.ufrgs.inf.prosoft.cache;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 *
 * @author romulo
 * @param <K>
 * @param <V>
 */
public class MultiCache<K, V> implements Cache<K, V> {

    private static final boolean CACHE_EVALUATE_PERFORMANCE = System.getenv("CACHE_EVENTS") == null || !System.getenv("CACHE_EVENTS").equals("false");
    private static final boolean CACHE_REGISTER_SIZE = System.getenv("CACHE_REGISTER_SIZE") != null && System.getenv("CACHE_REGISTER_SIZE").equals("true");
    private final HashMap<K, V> map;
    private final ConcurrentHashMap<Optional<K>, Long> keyHasTTL;
    private final CachePerformance cachePerformance;
    private ScheduledExecutorService batchEntriesInvalidator;

    public MultiCache() {
        this(new CachePerformance());
    }

    public MultiCache(String name) {
        this(new CachePerformance(name));
    }

    public MultiCache(CachePerformance cachingPerformance) {
        this.cachePerformance = cachingPerformance;
        this.map = new HashMap<>();
        this.keyHasTTL = new ConcurrentHashMap<>();
    }

    public CachePerformance getCachePerformance() {
        return this.cachePerformance;
    }

    @Override
    public void put(K key, V value, long timeToLive) {
        put(key, value);
        this.keyHasTTL.put(Optional.ofNullable(key), System.currentTimeMillis() + timeToLive);
        if (batchEntriesInvalidator == null) {
            batchEntriesInvalidator = Executors.newScheduledThreadPool(0);
            batchEntriesInvalidator.scheduleAtFixedRate(() -> {
                this.keyHasTTL.forEach((k, v) -> {
                    if (!isValid(k.orElse(null))) {
                        invalidate(k.orElse(null));
                    }
                });
                if (this.keyHasTTL.isEmpty()) {
                    this.batchEntriesInvalidator.shutdown();
                    this.batchEntriesInvalidator = null;
                }
            }, 10, 10, TimeUnit.SECONDS);
        }
    }

    @Override
    public void put(K key, V value) {
        invalidate(key);
        this.map.put(key, value);
        registerEvent(EventType.ADDITION, value);
    }

    @Override
    public V get(K key) throws KeyNotFoundException {
        if (!containsKey(key)) {
            registerEvent(EventType.MISS, key);
            throw new KeyNotFoundException();
        }
        V get = this.map.get(key);
        registerEvent(EventType.HIT, get);
        return get;
    }

    @Override
    public void invalidate(K key) {
        this.keyHasTTL.remove(Optional.ofNullable(key));
        if (this.map.containsKey(key)) {
            V remove = this.map.remove(key);
            registerEvent(EventType.INVALIDATION, remove);
        }
    }

    private boolean isValid(K key) {
        try {
            return System.currentTimeMillis() <= this.keyHasTTL.get(Optional.ofNullable(key));
        } catch (NullPointerException ex) {
            return true;
        }
    }

    public Set<Map.Entry<K, V>> entrySet() {
        Set<Map.Entry<K, V>> entrySet = this.map.entrySet().stream()
                .filter(entry -> isValid(entry.getKey()))
                .collect(Collectors.
                        toMap(entry -> entry.getKey(), entry -> entry.getValue())
                ).entrySet();
        if (CACHE_EVALUATE_PERFORMANCE) {
            entrySet.forEach(entry -> {
                registerEvent(EventType.HIT, entry.getValue());
            });
        }
        return entrySet;
    }

    public List<V> values() {
        List<V> values = this.map.entrySet().stream()
                .filter(entry -> isValid(entry.getKey()))
                .map(entry -> entry.getValue())
                .collect(Collectors.toList());
        if (CACHE_EVALUATE_PERFORMANCE) {
            values.forEach(get -> {
                registerEvent(EventType.HIT, get);
            });
        }
        return values;
    }

    private boolean containsKey(K key) {
        return this.map.containsKey(key) && isValid(key);
    }

    private String getIdentifier(Object object) {
        return object != null ? String.valueOf(object.hashCode()) : "null";
    }

    private void registerEvent(EventType eventType, Object object) {
        if (!CACHE_EVALUATE_PERFORMANCE) {
            return;
        }
        String identifier = getIdentifier(object);
        if (CACHE_REGISTER_SIZE) {
            this.cachePerformance.registerEvent(eventType, identifier, CachePerformance.calculateObjectSize(object));
        } else {
            this.cachePerformance.registerEvent(eventType, identifier);
        }
        if (eventType.equals(EventType.ADDITION)) {
            this.cachePerformance.registerSize(this.map.size());
        }
    }
}