
com.alicp.jetcache.MultiLevelCache Maven / Gradle / Ivy
The newest version!
package com.alicp.jetcache;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* Created on 16/9/13.
*
* @author huangli
*/
public class MultiLevelCache extends AbstractCache {
private Cache[] caches;
private MultiLevelCacheConfig config;
@SuppressWarnings("unchecked")
@Deprecated
public MultiLevelCache(Cache... caches) throws CacheConfigException {
this.caches = caches;
checkCaches();
CacheConfig lastConfig = caches[caches.length - 1].config();
config = new MultiLevelCacheConfig<>();
config.setCaches(Arrays.asList(caches));
config.setExpireAfterWriteInMillis(lastConfig.getExpireAfterWriteInMillis());
config.setCacheNullValue(lastConfig.isCacheNullValue());
}
@SuppressWarnings("unchecked")
public MultiLevelCache(MultiLevelCacheConfig cacheConfig) throws CacheConfigException {
this.config = cacheConfig;
this.caches = cacheConfig.getCaches().toArray(new Cache[]{});
checkCaches();
}
private void checkCaches() {
if (caches == null || caches.length == 0) {
throw new IllegalArgumentException();
}
for (Cache c : caches) {
if (c.config().getLoader() != null) {
throw new CacheConfigException("Loader on sub cache is not allowed, set the loader into MultiLevelCache.");
}
}
}
public Cache[] caches() {
return caches;
}
@Override
public MultiLevelCacheConfig config() {
return config;
}
@Override
public CacheResult PUT(K key, V value) {
if (config.isUseExpireOfSubCache()) {
return PUT(key, value, 0, null);
} else {
return PUT(key, value, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);
}
}
@Override
public CacheResult PUT_ALL(Map extends K, ? extends V> map) {
if (config.isUseExpireOfSubCache()) {
return PUT_ALL(map, 0, null);
} else {
return PUT_ALL(map, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);
}
}
@Override
protected CacheGetResult do_GET(K key) {
for (int i = 0; i < caches.length; i++) {
Cache cache = caches[i];
CacheGetResult result = cache.GET(key);
if (result.isSuccess()) {
CacheValueHolder holder = unwrapHolder(result.getHolder());
checkResultAndFillUpperCache(key, i, holder);
return new CacheGetResult(CacheResultCode.SUCCESS, null, holder);
}
}
return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;
}
private CacheValueHolder unwrapHolder(CacheValueHolder h) {
// if @Cached or @CacheCache change type from REMOTE to BOTH (or from BOTH to REMOTE),
// during the dev/publish process, the value type which different application server put into cache server will be different
// (CacheValueHolder and CacheValueHolder>, respectively).
// So we need correct the problem at here and in CacheGetResult.
Objects.requireNonNull(h);
if (h.getValue() instanceof CacheValueHolder) {
return (CacheValueHolder) h.getValue();
} else {
return h;
}
}
private void checkResultAndFillUpperCache(K key, int i, CacheValueHolder h) {
Objects.requireNonNull(h);
long currentExpire = h.getExpireTime();
long now = System.currentTimeMillis();
if (now <= currentExpire) {
if(config.isUseExpireOfSubCache()){
PUT_caches(i, key, h.getValue(), 0, null);
} else {
long restTtl = currentExpire - now;
if (restTtl > 0) {
PUT_caches(i, key, h.getValue(), restTtl, TimeUnit.MILLISECONDS);
}
}
}
}
@Override
protected MultiGetResult do_GET_ALL(Set extends K> keys) {
HashMap> resultMap = new HashMap<>();
Set restKeys = new HashSet<>(keys);
for (int i = 0; i < caches.length; i++) {
if (restKeys.size() == 0) {
break;
}
Cache> c = caches[i];
MultiGetResult> allResult = c.GET_ALL(restKeys);
if (allResult.isSuccess() && allResult.getValues() != null) {
for (Map.Entry>> en : allResult.getValues().entrySet()) {
K key = en.getKey();
CacheGetResult result = en.getValue();
if (result.isSuccess()) {
CacheValueHolder holder = unwrapHolder(result.getHolder());
checkResultAndFillUpperCache(key, i, holder);
resultMap.put(key, new CacheGetResult(CacheResultCode.SUCCESS, null, holder));
restKeys.remove(key);
}
}
}
}
for (K k : restKeys) {
resultMap.put(k, CacheGetResult.NOT_EXISTS_WITHOUT_MSG);
}
return new MultiGetResult<>(CacheResultCode.SUCCESS, null, resultMap);
}
@Override
protected CacheResult do_PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
return PUT_caches(caches.length, key, value, expireAfterWrite, timeUnit);
}
@Override
protected CacheResult do_PUT_ALL(Map extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit) {
CompletableFuture future = CompletableFuture.completedFuture(null);
for (Cache c : caches) {
CacheResult r;
if(timeUnit == null) {
r = c.PUT_ALL(map);
} else {
r = c.PUT_ALL(map, expireAfterWrite, timeUnit);
}
future = combine(future, r);
}
return new CacheResult(future);
}
private CacheResult PUT_caches(int lastIndex, K key, V value, long expire, TimeUnit timeUnit) {
CompletableFuture future = CompletableFuture.completedFuture(null);
for (int i = 0; i < lastIndex; i++) {
Cache cache = caches[i];
CacheResult r;
if (timeUnit == null) {
r = cache.PUT(key, value);
} else {
r = cache.PUT(key, value, expire, timeUnit);
}
future = combine(future, r);
}
return new CacheResult(future);
}
private CompletableFuture combine(CompletableFuture future, CacheResult result) {
return future.thenCombine(result.future(), (d1, d2) -> {
if (d1 == null) {
return d2;
}
if (d1.getResultCode() != d2.getResultCode()) {
return new ResultData(CacheResultCode.PART_SUCCESS, null, null);
}
return d1;
});
}
@Override
protected CacheResult do_REMOVE(K key) {
CompletableFuture future = CompletableFuture.completedFuture(null);
for (Cache cache : caches) {
CacheResult r = cache.REMOVE(key);
future = combine(future, r);
}
return new CacheResult(future);
}
@Override
protected CacheResult do_REMOVE_ALL(Set extends K> keys) {
CompletableFuture future = CompletableFuture.completedFuture(null);
for (Cache cache : caches) {
CacheResult r = cache.REMOVE_ALL(keys);
future = combine(future, r);
}
return new CacheResult(future);
}
@Override
public T unwrap(Class clazz) {
Objects.requireNonNull(clazz);
for (Cache cache : caches) {
try {
T obj = (T) cache.unwrap(clazz);
if (obj != null) {
return obj;
}
} catch (IllegalArgumentException e) {
// ignore
}
}
throw new IllegalArgumentException(clazz.getName());
}
@Override
public AutoReleaseLock tryLock(K key, long expire, TimeUnit timeUnit) {
if (key == null) {
return null;
}
return caches[caches.length - 1].tryLock(key, expire, timeUnit);
}
@Override
public boolean putIfAbsent(K key, V value) {
throw new UnsupportedOperationException("putIfAbsent is not supported by MultiLevelCache");
}
@Override
protected CacheResult do_PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
throw new UnsupportedOperationException("PUT_IF_ABSENT is not supported by MultiLevelCache");
}
@Override
public void close() {
super.close();
for (Cache c : caches) {
c.close();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy