diff --git a/common/src/main/java/com/moparisthebest/jdbc/cache/Cache.java b/common/src/main/java/com/moparisthebest/jdbc/cache/Cache.java new file mode 100644 index 0000000..045071c --- /dev/null +++ b/common/src/main/java/com/moparisthebest/jdbc/cache/Cache.java @@ -0,0 +1,60 @@ +package com.moparisthebest.jdbc.cache; + +import java.time.Duration; +import java.time.Instant; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * This implements a simple cache, meant to be accessed concurrently from multiple threads, that is refreshed on a user + * defined interval. + * + * Most often instances of this class will be `static final` so the entire application can benefit from the cache + * + * @param type of cached item + */ +public class Cache implements Supplier { + + public static Supplier of(final Duration refreshInterval, final Supplier supplier) { + return new Cache<>(refreshInterval, supplier); + } + + private final Duration refreshInterval; + private Instant lastRefresh; + + protected final Supplier supplier; + + protected T item; + + protected Cache(final Duration refreshInterval, final Supplier supplier) { + Objects.requireNonNull(refreshInterval); + // we are explicitly allowing supplier to be null, in which case get() will fail + this.refreshInterval = refreshInterval; + this.supplier = supplier; + // so refresh will be called immediately + this.lastRefresh = Instant.MIN; + } + + protected boolean shouldRefresh() { + return Instant.now().isAfter(lastRefresh.plus(refreshInterval)); + } + + protected void markRefreshed() { + this.lastRefresh = Instant.now(); + } + + @Override + public T get() { + if (shouldRefresh()) { + // we want to refresh + synchronized (this) { + if (shouldRefresh()) { + // maybe another thread beat us in here and already refreshed + this.item = supplier.get(); + markRefreshed(); + } + } + } + return item; + } +} diff --git a/common/src/main/java/com/moparisthebest/jdbc/cache/FunctionCache.java b/common/src/main/java/com/moparisthebest/jdbc/cache/FunctionCache.java new file mode 100644 index 0000000..acb8cf2 --- /dev/null +++ b/common/src/main/java/com/moparisthebest/jdbc/cache/FunctionCache.java @@ -0,0 +1,79 @@ +package com.moparisthebest.jdbc.cache; + +import com.moparisthebest.jdbc.Factory; + +import java.sql.SQLException; +import java.time.Duration; +import java.time.Instant; +import java.util.function.Function; +import java.util.function.Supplier; + +import static com.moparisthebest.jdbc.TryClose.tryClose; + +/** + * This implements a simple cache, meant to be accessed concurrently from multiple threads, that is refreshed on a user + * defined interval. + * + * Most often instances of this class will be `static final` so the entire application can benefit from the cache + * + * This adds a method on top of Cache that allows the caller to send in a parameter for the refreshing function to use, + * this will most often be used to send in an already-open java.sql.Connection or JdbcMapper instance + * + * @param type of cached item + */ +public class FunctionCache extends Cache implements FunctionSupplier { + + public static FunctionSupplier ofNotCloseable(final Duration refreshInterval, final Function function, final Supplier factory) { + return of(refreshInterval, function, () -> function.apply(factory.get())); + } + + public static FunctionSupplier ofSupplier(final Duration refreshInterval, final Function function, final Supplier factory) { + return ofFactory(refreshInterval, function, factory::get); + } + + public static FunctionSupplier ofFactory(final Duration refreshInterval, final Function function, final Factory factory) { + return of(refreshInterval, function, () -> { + F f = null; + try { + try { + f = factory.create(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + return function.apply(f); + } finally { + tryClose(f); + } + }); + } + + public static FunctionSupplier of(final Duration refreshInterval, final Function function, final Supplier supplier) { + return new FunctionCache<>(refreshInterval, function, supplier); + } + + public static Function of(final Duration refreshInterval, final Function function) { + return of(refreshInterval, function, null); + } + + protected final Function function; + + protected FunctionCache(final Duration refreshInterval, final Function function, final Supplier supplier) { + super(refreshInterval, supplier); + this.function = function; + } + + @Override + public T get(final F f) { + if (shouldRefresh()) { + // we want to refresh + synchronized (this) { + if (shouldRefresh()) { + // maybe another thread beat us in here and already refreshed + this.item = function.apply(f); + markRefreshed(); + } + } + } + return item; + } +} diff --git a/common/src/main/java/com/moparisthebest/jdbc/cache/FunctionSupplier.java b/common/src/main/java/com/moparisthebest/jdbc/cache/FunctionSupplier.java new file mode 100644 index 0000000..1f33322 --- /dev/null +++ b/common/src/main/java/com/moparisthebest/jdbc/cache/FunctionSupplier.java @@ -0,0 +1,14 @@ +package com.moparisthebest.jdbc.cache; + +import java.util.function.Function; +import java.util.function.Supplier; + +public interface FunctionSupplier extends Supplier, Function { + + T get(F f); + + @Override + default T apply(final F f) { + return get(f); + } +} diff --git a/pom.xml b/pom.xml index 4e84468..569497e 100644 --- a/pom.xml +++ b/pom.xml @@ -246,6 +246,7 @@ **/module-info.java **/PrestoPersonDAO.java + **/com/moparisthebest/jdbc/cache/* -Xlint:unchecked