org.killbill.commons.utils.cache.DefaultCache Maven / Gradle / Ivy
/*
* Copyright 2020-2022 Equinix, Inc
* Copyright 2014-2022 The Billing Project, LLC
*
* The Billing Project licenses this file to you 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.killbill.commons.utils.cache;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import org.killbill.commons.utils.Preconditions;
import org.killbill.commons.utils.annotation.VisibleForTesting;
/**
*
* Default {@link Cache} implementation, that provide ability to:
*
* - Add maxSize to the cache. If more entry added, the oldest entry get removed automatically
* -
* Add lazy-loading capability with {@code cacheLoader} parameter in constructor. This {@code cacheLoader}
* will be called if {@link #get(Object)} return {@code null}. {@code cacheLoader} also will take precedence
* over loader defined in {@link #getOrLoad(Object, Function)}.
*
* - Add timout (similar to expire-after-write in Guava and Caffeine) capability
*
*
* @param cache key
* @param cache value
*/
public class DefaultCache implements Cache {
public static final long NO_TIMEOUT = 0;
@VisibleForTesting
final Map> map;
private final long timeoutMillis;
private final Function cacheLoader;
/**
* Create cache with maximum entry size, without any timout (live forever) and no cache loader.
*
* @param maxSize max entry that should be existed in cache.
*/
public DefaultCache(final int maxSize) {
this(maxSize, NO_TIMEOUT, noCacheLoader());
}
/**
* Create cache with {@code maxSize = Integer.MAX_VALUE}, {@code timeoutInSecond = NO_TIMEOUT}, and with supplied
* {@code cacheLoader}.
*
* @param cacheLoader cache loader. Use {@link #noCacheLoader()} to make this cache should not attempt to load
* anything if value is null.
*/
public DefaultCache(final Function cacheLoader) {
this(Integer.MAX_VALUE, NO_TIMEOUT, cacheLoader);
}
/**
* Create cache with maximum entry size, timeout (in second), and cacheLoader capability.
*
* @param maxSize cache maximum size. If more entry added, the oldest entry get removed automatically.
* @param timeoutInSecond cache entry expire time. Entry will eventually be removed after specifics time defined
* here. Use {@link DefaultCache#NO_TIMEOUT} to make entry live forever.
* @param cacheLoader cache loader. Use {@link #noCacheLoader()} to make this cache should not attempt to load
* anything if value is null.
*/
public DefaultCache(final int maxSize, final long timeoutInSecond, final Function cacheLoader) {
Preconditions.checkArgument(maxSize > 0, "cache maxSize should > 0");
Preconditions.checkArgument(timeoutInSecond >= 0, "cache timeoutInSecond should >= 0");
this.timeoutMillis = timeoutInSecond * 1_000;
this.cacheLoader = Preconditions.checkNotNull(cacheLoader, "cacheLoader is null. Use DefaultCache#noCacheLoader() to create a cache without loader");
map = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(final Entry> eldest) {
return size() > maxSize;
}
};
}
/**
* Create {@link DefaultCache} without any loader.
*/
public static Function noCacheLoader() {
return k1 -> null;
}
protected boolean isTimeoutEnabled() {
return timeoutMillis > 0L;
}
protected boolean isCacheLoaderExist() {
return !noCacheLoader().equals(cacheLoader);
}
protected void evictExpireEntry(final K key) {
if (isTimeoutEnabled()) {
final TimedValue value = map.get(key);
if (value != null && value.isTimeout()) {
invalidate(key);
}
}
}
@Override
public V get(final K key) {
Preconditions.checkNotNull(key, "Cannot #get() cache with key = null");
evictExpireEntry(key);
final TimedValue timedValue = map.get(key);
if (timedValue != null) {
return timedValue.getValue();
} else if (isCacheLoaderExist()) {
final V value = cacheLoader.apply(key);
if (value != null) {
put(key, value);
}
return value;
} else {
return null;
}
}
@Override
public V getOrLoad(final K key, final Function loader) {
Preconditions.checkNotNull(loader, "loader parameter in #getOrLoad() is null");
final V value = get(key);
return value == null ? loader.apply(key) : value;
}
@Override
public void put(final K key, final V value) {
Preconditions.checkNotNull(key, "key in #put() is null");
Preconditions.checkNotNull(value, "value in #put() is null");
map.put(key, new TimedValue<>(timeoutMillis, value));
}
@Override
public void invalidate(final K key) {
Preconditions.checkNotNull(key, "Cannot invalidate. Cache with null key is not allowed");
map.remove(key);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy