io.helidon.security.providers.EvictableCache Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of helidon-security-providers-common Show documentation
Show all versions of helidon-security-providers-common Show documentation
Common utilities for providers.
/*
* Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
*
* 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.helidon.security.providers;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
import io.helidon.config.Config;
/**
* Generic cache with eviction and max size.
*
* @param type of keys in this cache
* @param type of values in this cache
*/
public interface EvictableCache {
/**
* Default timeout of records in minutes (inactivity timeout).
*/
long CACHE_TIMEOUT_MINUTES = 60;
/**
* Default eviction period in minutes (how often to evict records).
*/
long CACHE_EVICT_PERIOD_MINUTES = 5;
/**
* Default eviction delay in minutes (how long to wait after the cache is started).
*/
long CACHE_EVICT_DELAY_MINUTES = 1;
/**
* Maximal number of records in the cache.
* If the cache is full, no caching is done and the supplier of value is called for every uncached value.
*/
long CACHE_MAX_SIZE = 100_000;
/**
* Parameter to {@link ConcurrentHashMap#forEachKey(long, Consumer)} used for eviction.
*/
long EVICT_PARALLELISM_THRESHOLD = 10000;
/**
* Create a new builder for a cache.
*
* @param type of keys in the cache
* @param type of values in the cache
* @return a builder to build the cache
*/
static Builder builder() {
return new Builder<>();
}
/**
* Create a new cache with default values.
*
* @param type of keys in the cache
* @param type of values in the cache
* @return new cache built with default values
*/
static EvictableCache create() {
Builder builder = builder();
return builder.build();
}
/**
* Create a new cache and configure it from the provided configuration.
* See {@link Builder#fromConfig(Config)} for the list of configuration keys.
*
* @param config config to read configuration of this cache from
* @param type of keys in the cache
* @param type of values in the cache
* @return new cache configured from config
*/
static EvictableCache create(Config config) {
Builder builder = builder();
return builder.fromConfig(config)
.build();
}
/**
* This method has same semantics as {@link #create(Config)} and is here to allow automatic
* loading of instances from config using {@link Config#as(Class)}.
*
* @param config config to read configuration of this cache from
* @param type of keys in the cache
* @param type of values in the cache
* @return new cache configured from config
* @deprecated Do not use directly, please use {@link #create(Config)}
*/
@Deprecated
static EvictableCache from(Config config) {
return create(config);
}
/**
* Create a new cache that is not a cache (e.g. never caches, delegates
* all calls to the {@link Supplier} in {@link #computeValue(Object, Supplier)}.
*
* @param Type of keys
* @param Type of values
* @return a new instance that is not caching
*/
@SuppressWarnings("unchecked")
static EvictableCache noCache() {
// there is no support for restricted visibility in interface in current java version
return (EvictableCache) EvictableCacheImpl.NO_CACHE;
}
/**
* Remove a key from the cache. Return the value if it was cached and valid.
*
* @param key key to remove
* @return value if it was removed and valid, empty otherwise
*/
default Optional remove(K key) {
return Optional.empty();
}
/**
* Get current cached value if valid.
*
* @param key key to use
* @return current value in the cache or empty if not present (or invalid)
*/
default Optional get(K key) {
return Optional.empty();
}
/**
* Current size of the cache.
* As this cache is using {@link ConcurrentHashMap} as backing store, be aware that this value is not
* guaranteed to be consistent, as puts and removed may be happening in parallel.
*
* @return current size of the cache (including valid and invalid - not yet evicted - values)
*/
default int size() {
return 0;
}
/**
* Either return a cached value or compute it and cache it.
*
* @param key key to check/insert value for
* @param valueSupplier supplier called if the value is not yet cached, or is invalid
* @return current value from the cache, or computed value from the supplier
*/
default Optional computeValue(K key, Supplier> valueSupplier) {
return valueSupplier.get();
}
/**
* Close this cache.
* Carry out shutdown tasks (e.g. shutting down eviction thread).
*/
default void close() {
}
/**
* Builder to create instances of {@link EvictableCache}.
*
* @param types of keys used in the cache
* @param types of values used in the cache
*/
class Builder {
private boolean cacheEnabled = true;
private long cacheTimeout = CACHE_TIMEOUT_MINUTES;
private long cacheMaxSize = CACHE_MAX_SIZE;
private TimeUnit cacheTimeoutUnit = TimeUnit.MINUTES;
private long cacheEvictDelay = CACHE_EVICT_DELAY_MINUTES;
private long cacheEvictPeriod = CACHE_EVICT_PERIOD_MINUTES;
private TimeUnit cacheEvictTimeUnit = TimeUnit.MINUTES;
private long parallelismThreshold = EVICT_PARALLELISM_THRESHOLD;
private BiFunction evictor = (key, value) -> false;
/**
* Build a new instance of the cache based on configuration of this builder.
*
* @param key type
* @param value type
* @return a new instance of the cache
*/
@SuppressWarnings("unchecked")
public EvictableCache build() {
if (cacheEnabled) {
return new EvictableCacheImpl(this);
} else {
return noCache();
}
}
/**
* Configure record timeout since last modification.
*
* @param timeout timeout value
* @param timeoutUnit timeout unit
* @return updated builder instance
*/
public Builder timeout(long timeout, TimeUnit timeoutUnit) {
this.cacheTimeout = timeout;
this.cacheTimeoutUnit = timeoutUnit;
return this;
}
/**
* Configure maximal cache size.
*
* @param cacheMaxSize maximal number of records to store in the cache
* @return updated builder instance
*/
public Builder maxSize(long cacheMaxSize) {
this.cacheMaxSize = cacheMaxSize;
return this;
}
/**
* Configure eviction scheduling.
*
* @param evictDelay delay from the creation of the cache to first eviction
* @param evictPeriod how often to evict records
* @param evictTimeUnit time unit to use for these values
* @return updated builder instance
*/
public Builder evictSchedule(long evictDelay, long evictPeriod, TimeUnit evictTimeUnit) {
this.cacheEvictDelay = evictDelay;
this.cacheEvictPeriod = evictPeriod;
this.cacheEvictTimeUnit = evictTimeUnit;
return this;
}
/**
* Configure parallelism threshold.
*
* @param parallelismThreshold see {@link ConcurrentHashMap#forEachKey(long, Consumer)}
* @return updated builder instance
*/
public Builder parallelismThreshold(long parallelismThreshold) {
this.parallelismThreshold = parallelismThreshold;
return this;
}
/**
* Configure evictor to check if a records is still valid.
* This should be a fast way to check, as it is happening in a {@link ConcurrentHashMap#forEachKey(long, Consumer)}.
* This is also called during all get and remove operations to only return valid records.
*
* @param evictor evictor to use, return {@code true} for records that should be evicted, {@code false} for records
* that should stay in cache
* @return updated builder instance
*/
public Builder evictor(BiFunction evictor) {
this.evictor = evictor;
return this;
}
/**
* If the cacheEnabled is set to false, no caching will be done.
* Otherwise (default behavior) evictable caching will be used.
*
* @param cacheEnabled whether to enable this cache or not (true - enabled by default)
* @return updated builder instance
*/
public Builder cacheEnabled(boolean cacheEnabled) {
this.cacheEnabled = cacheEnabled;
return this;
}
/**
* Update this builder from configuration.
*
* Options expected under the current config node:
*
* Configuration parameters
* key default value description
* cache-enabled true Set to false to fully disable caching (and ignore other parameters)
*
* cache-timeout-millis {@value #CACHE_TIMEOUT_MINUTES} minutes Timeout of records in the cache
* in milliseconds
* cache-evict-delay-millis {@value #CACHE_EVICT_DELAY_MINUTES} minutes How long to wait with
* eviction after the cache is created in milliseconds
* cache-evict-period-millis {@value #CACHE_EVICT_PERIOD_MINUTES} minutes How often to evict
* records from the cache in milliseconds
* parallelism-treshold {@value #EVICT_PARALLELISM_THRESHOLD} see
* {@link #parallelismThreshold(long)}
* evictor-class A class that is instantiated and used as an evictor for this instance
*
*
* @param config Config to use to load configuration options for this builder
* @return updated builder instance
*/
public Builder fromConfig(Config config) {
config.get("cache-enabled").asOptionalBoolean().ifPresent(this::cacheEnabled);
if (cacheEnabled) {
config.get("cache-timeout-millis").asOptionalLong().ifPresent(timeout -> timeout(timeout, TimeUnit.MILLISECONDS));
long evictDelay = config.get("cache-evict-delay-millis").asLong(cacheEvictTimeUnit.toMillis(cacheEvictDelay));
long evictPeriod = config.get("cache-evict-period-millis").asLong(cacheEvictTimeUnit.toMillis(cacheEvictPeriod));
evictSchedule(evictDelay, evictPeriod, TimeUnit.MILLISECONDS);
config.get("parallelism-treshold").asOptionalLong().ifPresent(this::parallelismThreshold);
config.get("evictor-class").asOptional(Class.class).ifPresent(this::evictorClass);
}
return this;
}
@SuppressWarnings("unchecked")
private Builder evictorClass(Class aClass) {
// attempt to create an instance
try {
aClass.getMethod("apply", Object.class, Object.class);
Object anObject = aClass.getConstructor().newInstance();
evictor((BiFunction) anObject);
} catch (ReflectiveOperationException e) {
throw new SecurityException("Failed to create an evictor instance. Configured class: " + aClass.getName(), e);
}
return this;
}
long cacheTimeout() {
return cacheTimeout;
}
long cacheMaxSize() {
return cacheMaxSize;
}
TimeUnit cacheTimeoutUnit() {
return cacheTimeoutUnit;
}
long cacheEvictDelay() {
return cacheEvictDelay;
}
long cacheEvictPeriod() {
return cacheEvictPeriod;
}
TimeUnit cacheEvictTimeUnit() {
return cacheEvictTimeUnit;
}
long parallelismThreshold() {
return parallelismThreshold;
}
BiFunction evictor() {
return evictor;
}
}
}