![JAR search and dependency download from the Maven repository](/logo.png)
com.okta.sdk.impl.cache.DefaultCacheManager Maven / Gradle / Ivy
/*
* Copyright 2014 Stormpath, Inc.
* Modifications Copyright 2018 Okta, Inc.
*
* 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 com.okta.sdk.impl.cache;
import com.okta.commons.lang.Assert;
import com.okta.sdk.cache.Cache;
import com.okta.sdk.cache.CacheManager;
import com.okta.sdk.impl.util.SoftHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
* Very simple default {@code CacheManager} implementation that retains all created {@link Cache Cache} instances in
* an in-memory {@link ConcurrentMap ConcurrentMap}. By default, this implementation creates thread-safe
* {@link DefaultCache} instances via the {@link #createCache(String) createCache(name)} method, but this can be overridden
* by subclasses that wish to provide different Cache implementations.
* Clustering
* This implementation DOES NOT SUPPORT CLUSTERING.
*
* If your application is deployed on multiple hosts, it is
* strongly recommended that you configure the Okta SDK with a clustered {@code CacheManager}
* implementation so all of your application instances can utilize the same cache policy and see the same
* security/identity data. Some example clusterable caching projects: Hazelcast, Ehcache+Terracotta, Coherence,
* GigaSpaces, etc.
*
* This implementation is production-quality, but only recommended for single-node/single-JVM applications.
*
Time To Idle
* Time to Idle is the amount of time a cache entry may be idle - unused (not accessed) - before it will expire and
* no longer be available. If a cache entry is not accessed at all after this amount of time, it will be
* removed from the cache as soon as possible.
*
* This implementation's {@link #setDefaultTimeToIdle(Duration) defaultTimeToIdle}
* is {@code null}, which means that cache entries can potentially remain idle indefinitely. Note however that a
* cache entry can still be expunged due to other conditions (e.g. memory constraints, Time to Live setting, etc).
*
* The {@link #setDefaultTimeToIdle(Duration) defaultTimeToIdle} setting is only
* applied to newly created {@code Cache} instances. It does not affect already existing {@code Cache}s.
*
Time to Live
* Time to Live is the amount of time a cache entry may exist after first being created before it will expire and no
* longer be available. If a cache entry ever becomes older than this amount of time (regardless of how often
* it is accessed), it will be removed from the cache as soon as possible.
*
* This implementation's {@link #setDefaultTimeToLive(Duration) defaultTimeToLive}
* is {@code null}, which means that cache entries could potentially live indefinitely. Note however that a
* cache entry can still be expunged due to other conditions (e.g. memory constraints, Time to Idle setting, etc).
*
* The {@link #setDefaultTimeToLive(Duration) defaultTimeToLive} setting is only
* applied to newly created {@code Cache} instances. It does not affect already existing {@code Cache}s.
*
Thread Safety
* This implementation and the cache instances it creates are thread-safe and usable in concurrent environments.
*
* @see #setDefaultTimeToIdle(Duration)
* @see #setDefaultTimeToIdleSeconds(long)
* @see #setDefaultTimeToLive(Duration)
* @see #setDefaultTimeToLiveSeconds(long)
* @since 0.5.0
*/
public class DefaultCacheManager implements CacheManager {
private final Logger logger = LoggerFactory.getLogger(DefaultCacheManager.class);
/**
* Retains any region-specific configuration that might be used when creating Cache instances.
*/
private final ConcurrentMap configs;
/**
* Retains all Cache objects maintained by this cache manager.
*/
protected final ConcurrentMap caches;
private volatile Duration defaultTimeToLive;
private volatile Duration defaultTimeToIdle;
/**
* Default no-arg constructor that instantiates an internal name-to-cache {@code ConcurrentMap}.
*/
public DefaultCacheManager() {
this.configs = new ConcurrentHashMap<>();
this.caches = new ConcurrentHashMap<>();
}
/**
* Returns the default {@link DefaultCache#getTimeToLive() timeToLive} duration
* to apply to newly created {@link DefaultCache} instances. This setting does not affect existing
* {@link DefaultCache} instances.
*
* @return the default {@link DefaultCache#getTimeToLive() timeToLive} duration
* to apply to newly created {@link DefaultCache} instances.
* @see DefaultCache
* @see DefaultCache#getTimeToLive()
*/
public Duration getDefaultTimeToLive() {
return defaultTimeToLive;
}
/**
* Sets the default {@link DefaultCache#getTimeToLive() timeToLive} duration
* to apply to newly created {@link DefaultCache} instances. This setting does not affect existing
* {@link DefaultCache} instances.
*
* @param defaultTimeToLive the default {@link DefaultCache#getTimeToLive() timeToLive}
* duration to apply to newly created {@link DefaultCache} instances.
*/
public void setDefaultTimeToLive(Duration defaultTimeToLive) {
DefaultCache.assertTtl(defaultTimeToLive);
this.defaultTimeToLive = defaultTimeToLive;
}
/**
* Convenience method that sets the {@link #setDefaultTimeToLive(Duration) defaultTimeToLive}
* value using a {@code TimeUnit} of {@link TimeUnit#SECONDS}.
*
* @param seconds the {@link #setDefaultTimeToLive(Duration) defaultTimeToLive} value in seconds.
*/
public void setDefaultTimeToLiveSeconds(long seconds) {
setDefaultTimeToLive(Duration.ofSeconds(seconds));
}
/**
* Returns the default {@link DefaultCache#getTimeToIdle() timeToIdle} duration
* to apply to newly created {@link DefaultCache} instances. This setting does not affect existing
* {@link DefaultCache} instances.
*
* @return the default {@link DefaultCache#getTimeToIdle() timeToIdle} duration
* to apply to newly created {@link DefaultCache} instances.
*/
public Duration getDefaultTimeToIdle() {
return defaultTimeToIdle;
}
/**
* Sets the default {@link DefaultCache#getTimeToIdle() timeToIdle} duration
* to apply to newly created {@link DefaultCache} instances. This setting does not affect existing
* {@link DefaultCache} instances.
*
* @param defaultTimeToIdle the default {@link DefaultCache#getTimeToIdle() timeToIdle}
* duration to apply to newly created {@link DefaultCache} instances.
*/
public void setDefaultTimeToIdle(Duration defaultTimeToIdle) {
DefaultCache.assertTti(defaultTimeToIdle);
this.defaultTimeToIdle = defaultTimeToIdle;
}
/**
* Convenience method that sets the {@link #setDefaultTimeToIdle(Duration) defaultTimeToIdle}
* value using a {@code TimeUnit} of {@link TimeUnit#SECONDS}.
*
* @param seconds the {@link #setDefaultTimeToIdle(Duration) defaultTimeToIdle} value in seconds.
*/
public void setDefaultTimeToIdleSeconds(long seconds) {
setDefaultTimeToIdle(Duration.ofSeconds(seconds));
}
/**
* Sets cache-specific configuration entries, to be utilized when creating cache instances.
*
* @param configs cache-specific configuration entries, to be utilized when creating cache instances.
*/
public void setCacheConfigurations(Collection configs) {
Assert.notNull("Argument cannot be null. To remove all configuration, set an empty collection.");
this.configs.clear();
for (CacheConfiguration config : configs) {
this.configs.put(config.getName(), config);
}
}
/**
* Returns the cache with the specified {@code name}. If the cache instance does not yet exist, it will be lazily
* created, retained for further access, and then returned.
*
* @param name the name of the cache to acquire.
* @return the cache with the specified {@code name}.
* @throws IllegalArgumentException if the {@code name} argument is {@code null} or does not contain text.
*/
public Cache getCache(String name) throws IllegalArgumentException {
Assert.hasText(name, "Cache name cannot be null or empty.");
Cache cache;
cache = caches.get(name);
if (cache == null) {
logger.debug("Creating cache '{}'", name);
cache = createCache(name);
Cache existing = caches.putIfAbsent(name, cache);
if (existing != null) {
cache = existing;
}
}
//noinspection unchecked
return cache;
}
/**
* Creates a new {@code Cache} instance associated with the specified {@code name}.
*
* @param name the name of the cache to create
* @return a new {@code Cache} instance associated with the specified {@code name}.
*/
@SuppressWarnings("unchecked")
protected Cache createCache(String name) {
Duration ttl = this.defaultTimeToLive != null ? Duration.from(this.defaultTimeToLive) : null;
Duration tti = this.defaultTimeToIdle != null ? Duration.from(this.defaultTimeToIdle) : null;
CacheConfiguration config = this.configs.get(name);
if (config != null) {
Duration d = config.getTimeToLive();
if (d != null) {
ttl = d;
}
d = config.getTimeToIdle();
if (d != null) {
tti = d;
}
}
return new DefaultCache(name, new SoftHashMap(), ttl, tti);
}
public String toString() {
Collection values = caches.values();
StringBuilder sb = new StringBuilder()
.append("{\n")
.append(" \"cacheCount\": ").append(caches.size()).append(",\n")
.append(" \"defaultTimeToLive\": \"").append(toString(defaultTimeToLive)).append("\",\n")
.append(" \"defaultTimeToIdle\": \"").append(toString(defaultTimeToIdle)).append("\",\n")
.append(" \"caches\": [");
if (!caches.isEmpty()) {
sb.append("\n");
int i = 0;
for (Cache cache : values) {
if (i > 0) {
sb.append(",\n");
}
sb.append(cache.toString());
i++;
}
sb.append("\n ");
}
sb.append("]\n}");
return sb.toString();
}
private String toString(Duration d) {
return d != null ? d.toString() : "indefinite";
}
}