Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.turbospaces.ebean.JGroupsCacheManager Maven / Gradle / Ivy
package com.turbospaces.ebean;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.jctools.maps.NonBlockingHashMap;
import org.jgroups.blocks.MethodLookup;
import org.jgroups.blocks.RpcDispatcher;
import org.springframework.beans.factory.BeanNameAware;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.netflix.archaius.api.Config;
import com.turbospaces.cache.BlockhoundCacheWrapper;
import com.turbospaces.cfg.ApplicationProperties;
import com.turbospaces.common.PlatformUtil;
import io.ebean.BackgroundExecutor;
import io.ebean.DatabaseBuilder;
import io.ebean.cache.QueryCacheEntryValidate;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheConfig;
import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCacheStatistics;
import io.ebean.cache.ServerCacheType;
import io.ebean.config.CurrentTenantProvider;
import io.ebeaninternal.server.cache.DefaultServerCacheConfig;
import io.ebeaninternal.server.cache.DefaultServerQueryCache;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.cache.GuavaCacheMetrics;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class JGroupsCacheManager implements MethodLookup, CacheManager, ServerCacheNotify, BeanNameAware {
public static short METHOD_ON_CACHE_PUT = 1;
public static short METHOD_ON_CHANGE_REMOVE = 2;
public static short METHOD_ON_CACHE_CLEAR = 3;
public static short METHOD_ON_MODIFIED = 4;
public static short METHOD_ON_CACHE_CLEAR_ALL = 5;
public static short METHOD_ON_CHANGE_PUT_ALL = 6;
public static short METHOD_ON_CHANGE_REMOVE_ALL = 7;
public static ImmutableMap METHODS;
static {
try {
ImmutableMap.Builder b = ImmutableMap.builder();
b.put(METHOD_ON_MODIFIED, JGroupsCacheManager.class.getMethod("onTablesModify", String.class));
b.put(METHOD_ON_CACHE_PUT, JGroupsCacheManager.class.getMethod("onCachePut", String.class, byte[].class, byte[].class));
b.put(METHOD_ON_CHANGE_REMOVE, JGroupsCacheManager.class.getMethod("onCacheRemove", String.class, byte[].class));
b.put(METHOD_ON_CACHE_CLEAR, JGroupsCacheManager.class.getMethod("onCacheClear", String.class));
b.put(METHOD_ON_CACHE_CLEAR_ALL, JGroupsCacheManager.class.getMethod("onCacheClearAll", boolean.class));
b.put(METHOD_ON_CHANGE_PUT_ALL, JGroupsCacheManager.class.getMethod("onCachePutAll", String.class, byte[].class));
b.put(METHOD_ON_CHANGE_REMOVE_ALL, JGroupsCacheManager.class.getMethod("onCacheRemoveAll", String.class, byte[].class));
METHODS = b.build();
} catch (NoSuchMethodException err) {
throw new Error(err);
}
}
private final ApplicationProperties props;
private final MeterRegistry meterRegistry;
private final JGroupsBroadcastChannel broadcast;
private final ConcurrentMap caches;
private final ScheduledExecutorService timer;
private String beanName;
private BackgroundExecutor executor;
private ServerCacheNotify notify;
public JGroupsCacheManager(ApplicationProperties props, MeterRegistry meterRegistry, RpcDispatcher dispatcher) {
this.props = Objects.requireNonNull(props);
this.meterRegistry = Objects.requireNonNull(meterRegistry);
this.broadcast = new JGroupsBroadcastChannel(props, meterRegistry, dispatcher);
this.caches = new NonBlockingHashMap<>();
//
// ~ just one thread
//
this.timer = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
if (StringUtils.isNotEmpty(beanName)) {
t.setName(beanName);
}
return t;
}
});
long intervalMillis = props.CACHE_METRICS_REPORT_INTERVAL.get().toMillis();
timer.scheduleWithFixedDelay(() -> caches.forEach((key, value) -> {
if (key.endsWith(ServerCacheType.QUERY.code())) {
ServerCacheStatistics stats = value.statistics(true);
meterRegistry.counter("query_cache.hits", "name", key).increment(stats.getHitCount());
meterRegistry.counter("query_cache.puts", "name", key).increment(stats.getPutCount());
meterRegistry.counter("query_cache.evicts", "name", key).increment(stats.getEvictCount());
meterRegistry.gauge("query_cache.size", Tags.of("name", key), stats.getSize());
meterRegistry.gauge("query_cache.maxSize", Tags.of("name", key), stats.getMaxSize());
meterRegistry.gauge("query_cache.hit_ratio", Tags.of("name", key), stats.getHitRatio());
meterRegistry.counter("query_cache.miss_count", "name", key).increment(stats.getMissCount());
meterRegistry.counter("query_cache.ttl_count", "name", key).increment(stats.getTtlCount());
meterRegistry.counter("query_cache.remove_count", "name", key).increment(stats.getRemoveCount());
meterRegistry.counter("query_cache.clear_count", "name", key).increment(stats.getClearCount());
meterRegistry.counter("query_cache.idle_count", "name", key).increment(stats.getIdleCount());
}
}), intervalMillis, intervalMillis, TimeUnit.MILLISECONDS);
}
@Override
public void setBeanName(String name) {
this.beanName = Objects.requireNonNull(name);
}
@Override
public void destroy() throws Exception {
try {
broadcast.destroy();
caches.clear();
} finally {
PlatformUtil.shutdownExecutor(timer, props.APP_PLATFORM_GRACEFUL_SHUTDOWN_TIMEOUT.get());
}
}
@Override
public Method findMethod(short id) {
return METHODS.get(id);
}
@Override
public void setBackgroundExecutor(BackgroundExecutor executor) {
this.executor = Objects.requireNonNull(executor);
}
@Override
public ServerCacheFactory create(DatabaseBuilder databaseBuilder, BackgroundExecutor backgroundExecutor) {
setBackgroundExecutor(backgroundExecutor);
return this;
}
@Override
public ServerCacheNotify createCacheNotify(ServerCacheNotify cacheNotify) {
this.notify = Objects.requireNonNull(cacheNotify);
return this;
}
@Override
public void notify(ServerCacheNotification notification) {
if (CollectionUtils.isNotEmpty(notification.getDependentTables())) {
Set dependentTables = notification.getDependentTables();
String line = Joiner.on(',').join(dependentTables);
log.debug("Publish TableMods - {}", line);
broadcast.broadcastAsync(notification, line);
}
}
@Override
public void onTablesModify(String line) {
Iterable it = Splitter.on(',').omitEmptyStrings().split(line);
ImmutableSet tables = ImmutableSet.copyOf(it);
ServerCacheNotification notification = new ServerCacheNotification(tables);
if (Objects.nonNull(notify)) {
notify.notify(notification);
}
}
@Override
public void onCachePut(String cacheKey, byte[] key, byte[] value) throws Throwable {
AbstractEbeanCache cache = (AbstractEbeanCache) caches.get(cacheKey);
if (Objects.nonNull(cache)) {
cache.onPut(key, value);
}
}
@Override
public void onCacheRemove(String cacheKey, byte[] key) throws Throwable {
AbstractEbeanCache cache = (AbstractEbeanCache) caches.get(cacheKey);
if (Objects.nonNull(cache)) {
cache.onRemove(key);
}
}
@Override
public void onCachePutAll(String cacheKey, byte[] data) throws Throwable {
AbstractEbeanCache cache = (AbstractEbeanCache) caches.get(cacheKey);
if (Objects.nonNull(cache)) {
cache.onPutAll(data);
}
}
@Override
public void onCacheRemoveAll(String cacheKey, byte[] data) throws Throwable {
AbstractEbeanCache cache = (AbstractEbeanCache) caches.get(cacheKey);
if (Objects.nonNull(cache)) {
cache.onRemoveAll(data);
}
}
@Override
public void onCacheClear(String cacheKey) throws Throwable {
ServerCache cache = caches.get(cacheKey);
if (Objects.nonNull(cache)) {
if (cache instanceof ClearableCache) {
((ClearableCache) cache).onClear();
}
}
}
@Override
public void onCacheClearAll(boolean preserveSimple) throws Throwable {
onCacheClearAll();
}
@Override
public void onCacheClearAll() throws Throwable {
for (ServerCache cache : caches.values()) {
if (cache instanceof ClearableCache) {
((ClearableCache) cache).onClear();
}
}
}
@Override
public ServerCache getCache(String cacheKey) {
return caches.get(cacheKey);
}
@Override
public void clearAll() {
try {
onCacheClearAll();
} catch (Throwable err) {
log.error(err.getMessage(), err);
} finally {
broadcast.broadcastClearAllAsync();
}
}
@Override
public ServerCache createCache(ServerCacheConfig config) {
String cacheKey = config.getCacheKey();
String shortName = config.getShortName();
ServerCacheOptions cacheOptions = config.getCacheOptions();
ServerCacheType cacheType = config.getType();
CurrentTenantProvider tenantProvider = config.getTenantProvider();
QueryCacheEntryValidate queryCacheValidate = config.getQueryCacheEntryValidate();
ServerCache cache = caches.get(cacheKey);
if (Objects.isNull(cache)) {
Config prefixedView = props.cfg().getPrefixedView(cacheKey);
Map configMap = new HashMap<>();
for (String key : prefixedView.keys()) {
Object rawProperty = prefixedView.getRawProperty(key);
configMap.put(key, rawProperty);
}
//
// ~ optional cache settings
//
int maxTtl = prefixedView.getInteger(EbeanCacheConfigurer.MAX_TTL, cacheOptions.getMaxSecsToLive());
int maxIdle = prefixedView.getInteger(EbeanCacheConfigurer.MAX_IDLE, cacheOptions.getMaxIdleSecs());
int maxSize = prefixedView.getInteger(EbeanCacheConfigurer.MAX_SIZE, cacheOptions.getMaxSize());
int trimFrequency = (int) props.APP_TIMER_INTERVAL.get().toSeconds();
ServerCacheOptions options = new ServerCacheOptions();
options.setMaxSecsToLive(maxTtl);
options.setMaxIdleSecs(maxIdle);
options.setMaxSize(maxSize);
options.setTrimFrequency(trimFrequency);
ServerCacheConfig scc = new ServerCacheConfig(
cacheType,
cacheKey,
shortName,
options,
tenantProvider,
queryCacheValidate //
);
List tags = Lists.newArrayList(
Tag.of("cacheType", cacheType.name().toLowerCase()),
Tag.of("shortName", shortName)//
);
if (config.isQueryCache()) {
cache = new JgroupsServerQueryCache(props, new DefaultServerCacheConfig(scc), broadcast);
ServerCache prev = caches.putIfAbsent(cacheKey, cache);
if (Objects.nonNull(prev)) {
cache = prev;
} else {
log.debug("created query cache: {} using cfg {}",
cacheKey,
ToStringBuilder.reflectionToString(options, ToStringStyle.SHORT_PREFIX_STYLE));
}
} else {
//
// ~ fail fast in development mode and raise sentry error in production mode otherwise
//
if (BooleanUtils.isFalse(prefixedView.containsKey(EbeanCacheConfigurer.CACHE_MODE_LOCAL))) {
if (props.APP_DEV_MODE.get()) {
throw new IllegalStateException("cache %s is not configured".formatted(cacheKey));
}
log.error("cache {} is not configured", cacheKey);
}
boolean localMode = prefixedView.getBoolean(EbeanCacheConfigurer.CACHE_MODE_LOCAL, false);
ToStringStyle nameStyle = ToStringStyle.NO_CLASS_NAME_STYLE;
Cache holder = LocalCache.cache(cacheKey, new DefaultServerCacheConfig(scc));
BlockhoundCacheWrapper cacheWrapper = new BlockhoundCacheWrapper<>(holder);
if (localMode) {
cache = new LocalEbeanCache(props, cacheKey, cacheWrapper, tenantProvider, scc, broadcast);
ServerCache prev = caches.putIfAbsent(cacheKey, cache);
if (Objects.nonNull(prev)) {
cache = prev;
} else {
new GuavaCacheMetrics<>(holder, cacheKey, tags).bindTo(meterRegistry);
log.debug("created local cache: {} using cfg {}", cacheKey, ToStringBuilder.reflectionToString(options, nameStyle));
}
} else {
cache = new ReplicatedEbeanCache(props, cacheKey, cacheWrapper, tenantProvider, scc, broadcast);
ServerCache prev = caches.putIfAbsent(cacheKey, cache);
if (Objects.nonNull(prev)) {
cache = prev;
} else {
new GuavaCacheMetrics<>(holder, cacheKey, tags).bindTo(meterRegistry);
log.debug("created replicated cache: {} using cfg {}", cacheKey, ToStringBuilder.reflectionToString(options, nameStyle));
}
}
}
//
// ~ cleanUp
//
ServerCache tmp = Objects.requireNonNull(cache);
//
// ~ might be provided by framework directly, if not wrap into own
//
BackgroundExecutor toApply = executor;
if (Objects.isNull(toApply)) {
toApply = new BackgroundExecutor() {
@Override
public Future> submit(Runnable task) {
return timer.submit(task);
}
@Override
public Future submit(Callable task) {
return timer.submit(task);
}
@Override
public ScheduledFuture> scheduleWithFixedDelay(Runnable task, long initialDelay, long delay, TimeUnit unit) {
return timer.scheduleWithFixedDelay(task, initialDelay, delay, unit);
}
@Override
public ScheduledFuture> scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit) {
return timer.scheduleAtFixedRate(task, initialDelay, period, unit);
}
@Override
public ScheduledFuture schedule(Callable task, long delay, TimeUnit unit) {
return timer.schedule(task, delay, unit);
}
@Override
public ScheduledFuture> schedule(Runnable task, long delay, TimeUnit unit) {
return timer.schedule(task, delay, unit);
}
@Override
public void execute(Runnable task) {
timer.execute(task);
}
};
}
toApply.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
if (tmp instanceof LocalCache) {
((LocalCache) tmp).cleanUp();
} else if (tmp instanceof DefaultServerQueryCache) {
((DefaultServerQueryCache) tmp).runEviction();
}
}
}, trimFrequency, trimFrequency, TimeUnit.SECONDS);
}
return cache;
}
}