All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.moznion.auto_refresh_cache.AutoRefreshCache Maven / Gradle / Ivy

package net.moznion.auto_refresh_cache;

import lombok.extern.slf4j.Slf4j;

import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.function.Supplier;

/**
 * Cached object that can be refreshed automatically when cache is expired.
 * 

* It holds the same object until cache is expired, * and it refreshes the object by given supplier automatically when cache is expired. * * @param Type of cache object. */ @Slf4j public class AutoRefreshCache { private final long discardIntervalSec; private final Supplier supplier; private final Semaphore semaphore; private final boolean useBeforeCacheOnException; private volatile long expiresAt; private volatile T cached; /** * A constructor. * * @param discardIntervalSec Lifetime of cache object (seconds). If this interval is over, cache object will be refreshed by given supplier. * @param supplier Supplier of cache object. This supplier is used when making a cache object and refreshing one. * @param useBeforeCacheOnException If true, it returns already cached object and suppresses exception when supplier raises some exception. Otherwise, it throws exception as it is. */ public AutoRefreshCache(final long discardIntervalSec, final boolean useBeforeCacheOnException, final Supplier supplier) { this(supplier.get(), discardIntervalSec, useBeforeCacheOnException, supplier); } /** * A constructor with initial cached object. * * @param init the initial cached object. * @param discardIntervalSec Lifetime of cache object (seconds). If this interval is over, cache object will be refreshed by given supplier. * @param supplier Supplier of cache object. This supplier is used when making a cache object and refreshing one. * @param useBeforeCacheOnException If true, it returns already cached object and suppresses exception when supplier raises some exception. Otherwise, it throws exception as it is. */ public AutoRefreshCache(final T init, final long discardIntervalSec, final boolean useBeforeCacheOnException, final Supplier supplier) { this.discardIntervalSec = discardIntervalSec; this.supplier = supplier; this.useBeforeCacheOnException = useBeforeCacheOnException; cached = init; expiresAt = getExpiresAt(discardIntervalSec); semaphore = new Semaphore(1); } /** * Get cached or refreshed object. * * @return When cache is alive, it returns a cached object. Otherwise, it returns an object that is refreshed by supplier. */ public T get() { return get(getCurrentEpoch(), false); } /** * Get cached object. And schedule to refresh cache if cache is expired. *

* This method doesn't get refreshed cache object even if cache is expired; refreshed cache object will be available from the next calling. * When cache is expired, this method delegates to refresh processing to another thread. * It means refreshing processing is executed as asynchronous on other thread. *

* To describe in other words, this method retrieves always already cached object. * And schedules a task to refresh cache when cache is expired. * * @return Already cached object. */ public T getWithRefreshScheduling() { return get(getCurrentEpoch(), true); } /** * Get refreshed object. *

* It returns always refreshed object and extends lifetime. * But if other thread is attempting to refresh, this method returns the cached object that is not refreshed. *

* This method runs as exclusive between threads to ensure atomicity of updating cached object and lifetime. * * @return Refreshed object. */ public T forceGet() { return forceGet(false); } /** * Get cached object with scheduling to refresh cache always. *

* This method doesn't get refreshed cache object; refreshed cache object will be available from the next calling. * This method delegates to refresh processing to another thread. * It means refreshing processing is executed as asynchronous on other thread. *

* To describe in other words, this method retrieves always already cached object. * And schedules a task to refresh cache. * * @return Already cached object. */ public T forceGetWithRefreshScheduling() { return forceGet(true); } T get(final long currentEpoch, final boolean isDelayed) { if (expiresAt < currentEpoch) { // Expired. Fill new instance return forceGet(isDelayed); } return cached; } T forceGet(final boolean isScheduledRefreshing) { if (!semaphore.tryAcquire()) { // If attempt to get cached object while refreshing that by other thread, current thread returns old cache. return cached; } final Runnable refresher = () -> { try { this.cached = supplier.get(); } catch (RuntimeException e) { if (!useBeforeCacheOnException) { semaphore.release(); throw e; } log.warn("Failed to refresh cache so use old cache", e); } expiresAt = getExpiresAt(discardIntervalSec); semaphore.release(); }; if (isScheduledRefreshing) { final T currentCached = cached; final ExecutorService pool = Executors.newFixedThreadPool(1); pool.submit(refresher); pool.shutdown(); return currentCached; } refresher.run(); return cached; } private static long getCurrentEpoch() { return Instant.now().getEpochSecond(); } private static long getExpiresAt(final long discardIntervalSec) { return getCurrentEpoch() + discardIntervalSec; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy