/**
* Copyright 2016 Nikita Koksharov
*
* 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 org.redisson.spring.cache;
import java.lang.reflect.Constructor;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonCache implements Cache {
private RMapCache<Object, Object> mapCache;
private final RMap<Object, Object> map;
private CacheConfig config;
private final AtomicLong hits = new AtomicLong();
private final AtomicLong misses = new AtomicLong();
public RedissonCache(RMapCache<Object, Object> mapCache, CacheConfig config) {
this.mapCache = mapCache;
this.map = mapCache;
this.config = config;
}
public RedissonCache(RMap<Object, Object> map) {
this.map = map;
}
@Override
public String getName() {
return map.getName();
}
@Override
public RMap<?, ?> getNativeCache() {
return map;
}
@Override
public ValueWrapper get(Object key) {
Object value = map.get(key);
if (value == null) {
addCacheMiss();
}else{
addCacheHit();
}
return toValueWrapper(value);
}
public <T> T get(Object key, Class<T> type) {
Object value = map.get(key);
if (value == null) {
addCacheMiss();
}else{
addCacheHit();
if (value.getClass().getName().equals(NullValue.class.getName())) {
return null;
}
if (type != null && !type.isInstance(value)) {
throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value);
}
}
return (T) fromStoreValue(value);
}
@Override
public void put(Object key, Object value) {
value = toStoreValue(value);
if (mapCache != null) {
mapCache.fastPut(key, value, config.getTTL(), TimeUnit.MILLISECONDS, config.getMaxIdleTime(), TimeUnit.MILLISECONDS);
} else {
map.fastPut(key, value);
}
}
public ValueWrapper putIfAbsent(Object key, Object value) {
value = toStoreValue(value);
Object prevValue;
if (mapCache != null) {
prevValue = mapCache.putIfAbsent(key, value, config.getTTL(), TimeUnit.MILLISECONDS, config.getMaxIdleTime(), TimeUnit.MILLISECONDS);
} else {
prevValue = map.putIfAbsent(key, value);
}
return toValueWrapper(prevValue);
}
@Override
public void evict(Object key) {
map.fastRemove(key);
}
@Override
public void clear() {
map.clear();
}
private ValueWrapper toValueWrapper(Object value) {
if (value == null) {
return null;
}
if (value.getClass().getName().equals(NullValue.class.getName())) {
return NullValue.INSTANCE;
}
return new SimpleValueWrapper(value);
}
public <T> T get(Object key, Callable<T> valueLoader) {
Object value = map.get(key);
if (value == null) {
addCacheMiss();
RLock lock = map.getLock(key);
lock.lock();
try {
value = map.get(key);
if (value == null) {
try {
value = toStoreValue(valueLoader.call());
} catch (Exception ex) {
try {
Class<?> c = Class.forName("org.springframework.cache.Cache$ValueRetrievalException");
Constructor<?> constructor = c.getConstructor(Object.class, Callable.class, Throwable.class);
RuntimeException exception = (RuntimeException) constructor.newInstance(key, valueLoader, ex.getCause());
throw exception;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
put(key, value);
}
} finally {
lock.unlock();
}
}else{
addCacheHit();
}
return (T) fromStoreValue(value);
}
protected Object fromStoreValue(Object storeValue) {
if (storeValue instanceof NullValue) {
return null;
}
return storeValue;
}
protected Object toStoreValue(Object userValue) {
if (userValue == null) {
return NullValue.INSTANCE;
}
return userValue;
}
/** The number of get requests that were satisfied by the cache.
* @return the number of hits
*/
long getCacheHits(){
return hits.get();
}
/** A miss is a get request that is not satisfied.
* @return the number of misses
*/
long getCacheMisses(){
return misses.get();
}
private void addCacheHit(){
hits.incrementAndGet();
}
private void addCacheMiss(){
misses.incrementAndGet();
}
}