io.micronaut.cache.DefaultSyncCache Maven / Gradle / Ivy
/*
* Copyright 2017-2020 original authors
*
* 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 io.micronaut.cache;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Policy;
import com.github.benmanes.caffeine.cache.Weigher;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.annotation.EachBean;
import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.type.Argument;
import io.micronaut.inject.qualifiers.Qualifiers;
import io.reactivex.Flowable;
import org.reactivestreams.Publisher;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import javax.inject.Inject;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* A default {@link SyncCache} implementation based on Caffeine
*
*
Since Caffeine is a non-blocking in-memory cache the {@link #async()} method will return an implementation that
* runs operations in the current thread.
*
* @author Graeme Rocher
* @since 1.0
*/
@EachBean(CacheConfiguration.class)
public class DefaultSyncCache implements SyncCache {
private final CacheConfiguration cacheConfiguration;
private final com.github.benmanes.caffeine.cache.Cache cache;
private final ApplicationContext applicationContext;
private final ConversionService> conversionService;
/**
* Construct a sync cache implementation with given configurations.
*
* @param cacheConfiguration The cache configurations
* @param applicationContext The application context
* @param conversionService To convert the value from the cache into given required type
*/
public DefaultSyncCache(
DefaultCacheConfiguration cacheConfiguration,
ApplicationContext applicationContext,
ConversionService> conversionService) {
this((CacheConfiguration) cacheConfiguration, applicationContext, conversionService);
}
/**
* Construct a sync cache implementation with given configurations.
*
* @param cacheConfiguration The cache configurations
* @param applicationContext The application context
* @param conversionService To convert the value from the cache into given required type
*/
@Inject
public DefaultSyncCache(
CacheConfiguration cacheConfiguration,
ApplicationContext applicationContext,
ConversionService> conversionService) {
this.cacheConfiguration = cacheConfiguration;
this.applicationContext = applicationContext;
this.conversionService = conversionService;
this.cache = buildCache(cacheConfiguration);
}
@Override
public Publisher getCacheInfo() {
return Flowable.just(new CacheInfo() {
@NonNull
@Override
public String getName() {
return cacheConfiguration.getCacheName();
}
@NonNull
@Override
public Map get() {
Map data = new LinkedHashMap<>(2);
data.put("implementationClass", getNativeCache().getClass().getName());
data.put("caffeine", getCaffeineCacheData(cache));
return data;
}
});
}
@Override
public String getName() {
return cacheConfiguration.getCacheName();
}
@Override
public com.github.benmanes.caffeine.cache.Cache getNativeCache() {
return cache;
}
@Override
public Optional get(Object key, Argument requiredType) {
Object value = cache.getIfPresent(key);
if (value != null) {
return conversionService.convert(value, ConversionContext.of(requiredType));
}
return Optional.empty();
}
@Override
public T get(Object key, Argument requiredType, Supplier supplier) {
Object value = cache.get(key, o -> supplier.get());
if (value != null) {
Optional converted = conversionService.convert(value, ConversionContext.of(requiredType));
return converted.orElseThrow(() ->
new IllegalArgumentException("Cache supplier returned a value that cannot be converted to type: " + requiredType.getName())
);
}
return (T) value;
}
@Override
public void invalidate(Object key) {
cache.invalidate(key);
}
@Override
public void invalidateAll() {
cache.invalidateAll();
}
/**
* Cache the specified value using the specified key. If the value is null, it will call
* {@link #invalidate(Object)} passing the key
*
* @param key the key with which the specified value is to be associated
* @param value the value to be associated with the specified key
*/
@SuppressWarnings("unchecked")
@Override
public void put(@NonNull Object key, @Nullable Object value) {
if (value == null) {
// null is the same as removal
cache.invalidate(key);
} else {
cache.put(key, value);
}
}
@SuppressWarnings("unchecked")
@Override
public Optional putIfAbsent(Object key, T value) {
Object previous = cache.asMap().putIfAbsent(key, value);
return Optional.ofNullable((T) previous);
}
@SuppressWarnings("unchecked")
@Override
public T putIfAbsent(Object key, Supplier value) {
Object val = cache.asMap().computeIfAbsent(key, (k) -> value.get());
return (T) val;
}
/**
* Build a cache from the given configurations.
*
* @param cacheConfiguration The cache configurations
* @return cache
*/
protected com.github.benmanes.caffeine.cache.Cache buildCache(CacheConfiguration cacheConfiguration) {
Caffeine