/*
* Copyright 2013 Andriy Vityuk
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.vityuk.ginger.cache;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import static com.vityuk.ginger.util.Preconditions.checkArgument;
import static com.vityuk.ginger.util.Preconditions.checkNotNull;
/**
* This is basic {@link LoadingCache} implementation with thread local storage. It can be useful for caching
* non-thread safe resources.
*
* @param <K> - type of cache key
* @param <V> - type of cached value
* @author Andriy Vityuk
*/
public abstract class ThreadLocalLoadingCache<K, V> extends AbstractLoadingCache<K, V> {
private final ThreadLocal<Map<K, Object>> threadLocalCache = new ThreadLocal<Map<K, Object>>() {
@Override
protected Map<K, Object> initialValue() {
return new HashMap<K, Object>();
}
};
private final CacheLoader<K, V> cacheLoader;
/**
* Create {@code ThreadLocalLoadingCache} instance.
*
* @param cacheLoader - cache loader, must be not null
* @param <K> - key type
* @param <V> - value type
* @return cache instance
*/
public static <K, V> ThreadLocalLoadingCache<K, V> create(CacheLoader<K, V> cacheLoader) {
return new DefaultThreadLocalLoadingCache<K, V>(checkNotNull(cacheLoader));
}
/**
* Create expireable {@code ThreadLocalLoadingCache} instance.
*
* @param cacheLoader - cache loader, must be not null
* @param duration - amount of time after which entry considered expired
* @param unit - time unit of the duration
* @param <K> - key type
* @param <V> - value type
* @return cache instance
*/
public static <K, V> ThreadLocalLoadingCache<K, V> create(CacheLoader<K, V> cacheLoader, long duration, TimeUnit unit) {
checkNotNull(cacheLoader);
checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit);
return new ExpireableThreadLocalLoadingCache<K, V>(cacheLoader, unit.toNanos(duration));
}
private ThreadLocalLoadingCache(CacheLoader<K, V> cacheLoader) {
this.cacheLoader = checkNotNull(cacheLoader);
}
protected abstract void storeInCache(Map<K, Object> cache, K key, V value);
protected abstract V getFromCache(Map<K, Object> cache, Object key);
@Override
public V get(K key) throws ExecutionException {
Map<K, Object> cache = getCache();
V value = getFromCache(cache, checkNotNull(key));
if (value != null) {
return value;
}
V loadedValue;
try {
loadedValue = checkNotNull(cacheLoader.load(key));
} catch (Exception e) {
throw new ExecutionException(e);
}
storeInCache(cache, key, loadedValue);
return loadedValue;
}
private Map<K, Object> getCache() {
return threadLocalCache.get();
}
private static class DefaultThreadLocalLoadingCache<K, V> extends ThreadLocalLoadingCache<K, V> {
public DefaultThreadLocalLoadingCache(CacheLoader<K, V> cacheLoader) {
super(cacheLoader);
}
@Override
protected void storeInCache(Map<K, Object> cache, K key, V value) {
cache.put(key, value);
}
@Override
protected V getFromCache(Map<K, Object> cache, Object key) {
@SuppressWarnings("unchecked")
V value = (V) cache.get(key);
return value;
}
}
private static class ExpireableThreadLocalLoadingCache<K, V> extends ThreadLocalLoadingCache<K, V> {
private final long expireInNanos;
public ExpireableThreadLocalLoadingCache(CacheLoader<K, V> cacheLoader, long expireInNanos) {
super(cacheLoader);
this.expireInNanos = expireInNanos;
}
@Override
protected void storeInCache(Map<K, Object> cache, K key, V value) {
ExpireableValue<V> expireableValue = new ExpireableValue<V>(value, expireInNanos);
cache.put(key, expireableValue);
}
@Override
protected V getFromCache(Map<K, Object> cache, Object key) {
@SuppressWarnings("unchecked")
ExpireableValue<V> expireableValue = (ExpireableValue<V>) cache.get(key);
if (expireableValue == null) {
return null;
}
if (expireableValue.isExpired()) {
cache.remove(key);
return null;
}
return expireableValue.getValue();
}
}
}