MultiCache.java
Home
/
src /
main /
java /
br /
ufrgs /
inf /
prosoft /
cache /
MultiCache.java
/*
* 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 String CACHE_EVENTS = System.getenv("CACHE_EVENTS") != null && !System.getenv("CACHE_EVENTS").isEmpty() ? System.getenv("CACHE_EVENTS") : null;
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;
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<>();
ScheduledExecutorService batchEntriesInvalidator = Executors.newScheduledThreadPool(1);
batchEntriesInvalidator.scheduleAtFixedRate(() -> {
this.keyHasTTL.forEach((key, value) -> {
if (!isValid(key.orElse(null))) {
invalidate(key.orElse(null));
}
});
}, 10, 10, TimeUnit.SECONDS);
}
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);
}
@Override
public void put(K key, V value) {
invalidate(key);
this.map.put(key, value);
registerEvent(EventType.ADDITION, value);
this.cachePerformance.registerSize(this.map.size());
}
@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();
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());
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_EVENTS == null) {
return;
}
String identifier = getIdentifier(object);
if (CACHE_REGISTER_SIZE) {
this.cachePerformance.registerEvent(eventType, identifier, CachePerformance.calculateObjectSize(object));
} else {
this.cachePerformance.registerEvent(eventType, identifier);
}
}
}