/*
* Copyright 2015 Ben Manes. 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 com.github.benmanes.caffeine.jcache.configuration;
import static java.util.Objects.requireNonNull;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.cache.configuration.Factory;
import javax.cache.configuration.FactoryBuilder;
import javax.cache.configuration.MutableCacheEntryListenerConfiguration;
import javax.cache.event.CacheEntryEventFilter;
import javax.cache.event.CacheEntryListener;
import javax.cache.expiry.Duration;
import javax.cache.expiry.EternalExpiryPolicy;
import javax.cache.expiry.ExpiryPolicy;
import javax.inject.Inject;
import com.github.benmanes.caffeine.jcache.expiry.JCacheExpiryPolicy;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
/**
* Static utility methods pertaining to externalized {@link CaffeineConfiguration} entries using the
* Typesafe Config library.
*
* @author ben.manes@gmail.com (Ben Manes)
*/
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public final class TypesafeConfigurator {
static final Logger logger = Logger.getLogger(TypesafeConfigurator.class.getName());
static FactoryCreator factoryCreator = FactoryBuilder::factoryOf;
private TypesafeConfigurator() {}
/**
* Retrieves the default cache settings from the configuration resource.
*
* @param config the configuration resource
* @param <K> the type of keys maintained the cache
* @param <V> the type of cached values
* @return the default configuration for a cache
*/
public static <K, V> CaffeineConfiguration<K, V> defaults(Config config) {
return new Configurator<K, V>(config, "default").configure();
}
/**
* Retrieves the cache's settings from the configuration resource if defined.
*
* @param config the configuration resource
* @param cacheName the name of the cache
* @param <K> the type of keys maintained the cache
* @param <V> the type of cached values
* @return the configuration for the cache
*/
public static <K, V> Optional<CaffeineConfiguration<K, V>> from(Config config, String cacheName) {
CaffeineConfiguration<K, V> configuration = null;
try {
if (config.hasPath("caffeine.jcache." + cacheName)) {
configuration = new Configurator<K, V>(config, cacheName).configure();
}
} catch (ConfigException.BadPath e) {
logger.log(Level.WARNING, "Failed to load cache configuration", e);
}
return Optional.ofNullable(configuration);
}
/**
* Specifies how {@link Factory} instances are created for a given class name. The default
* strategy uses {@link Class#newInstance()} and requires the class has a no-args constructor.
*
* @param factoryCreator the strategy for creating a factory
*/
@Inject
public static void setFactoryCreator(FactoryCreator factoryCreator) {
TypesafeConfigurator.factoryCreator = requireNonNull(factoryCreator);
}
/** A one-shot builder for creating a configuration instance. */
private static final class Configurator<K, V> {
final CaffeineConfiguration<K, V> configuration;
final Config rootConfig;
final Config config;
Configurator(Config config, String cacheName) {
this.rootConfig = requireNonNull(config);
this.configuration = new CaffeineConfiguration<>();
this.config = rootConfig.getConfig("caffeine.jcache." + cacheName)
.withFallback(rootConfig.getConfig("caffeine.jcache.default"));
}
/** Returns a configuration built from the external settings. */
CaffeineConfiguration<K, V> configure() {
addStoreByValue();
addListeners();
addReadThrough();
addWriteThrough();
addMonitoring();
addLazyExpiration();
addEagerExpiration();
addRefresh();
addMaximum();
return configuration;
}
/** Adds the store-by-value settings. */
private void addStoreByValue() {
boolean enabled = config.getBoolean("store-by-value.enabled");
configuration.setStoreByValue(enabled);
if (config.hasPath("store-by-value.strategy")) {
configuration.setCopierFactory(factoryCreator.factoryOf(
config.getString("store-by-value.strategy")));
}
}
/** Adds the entry listeners settings. */
private void addListeners() {
for (String path : config.getStringList("listeners")) {
Config listener = rootConfig.getConfig(path);
Factory<? extends CacheEntryListener<? super K, ? super V>> listenerFactory =
factoryCreator.factoryOf(listener.getString("class"));
Factory<? extends CacheEntryEventFilter<? super K, ? super V>> filterFactory = null;
if (listener.hasPath("filter")) {
filterFactory = factoryCreator.factoryOf(listener.getString("filter"));
}
boolean oldValueRequired = listener.getBoolean("old-value-required");
boolean synchronous = listener.getBoolean("synchronous");
configuration.addCacheEntryListenerConfiguration(
new MutableCacheEntryListenerConfiguration<>(
listenerFactory, filterFactory, oldValueRequired, synchronous));
}
}
/** Adds the read through settings. */
private void addReadThrough() {
boolean isReadThrough = config.getBoolean("read-through.enabled");
configuration.setReadThrough(isReadThrough);
if (config.hasPath("read-through.loader")) {
String loaderClass = config.getString("read-through.loader");
configuration.setCacheLoaderFactory(factoryCreator.factoryOf(loaderClass));
}
}
/** Adds the write through settings. */
private void addWriteThrough() {
boolean isWriteThrough = config.getBoolean("write-through.enabled");
configuration.setWriteThrough(isWriteThrough);
if (config.hasPath("write-through.writer")) {
String writerClass = config.getString("write-through.writer");
configuration.setCacheWriterFactory(factoryCreator.factoryOf(writerClass));
}
}
/** Adds the JMX monitoring settings. */
private void addMonitoring() {
configuration.setStatisticsEnabled(config.getBoolean("monitoring.statistics"));
configuration.setManagementEnabled(config.getBoolean("monitoring.management"));
}
/** Adds the JCache specification's lazy expiration settings. */
public void addLazyExpiration() {
Duration creation = getDurationFor("policy.lazy-expiration.creation");
Duration update = getDurationFor("policy.lazy-expiration.update");
Duration access = getDurationFor("policy.lazy-expiration.access");
boolean eternal = Objects.equals(creation, Duration.ETERNAL)
&& Objects.equals(update, Duration.ETERNAL)
&& Objects.equals(access, Duration.ETERNAL);
Factory<? extends ExpiryPolicy> factory = eternal
? EternalExpiryPolicy.factoryOf()
: FactoryBuilder.factoryOf(new JCacheExpiryPolicy(creation, update, access));
configuration.setExpiryPolicyFactory(factory);
}
/** Returns the duration for the expiration time. */
private @Nullable Duration getDurationFor(String path) {
if (!config.hasPath(path)) {
return null;
}
if (config.getString(path).equalsIgnoreCase("eternal")) {
return Duration.ETERNAL;
}
long millis = config.getDuration(path, TimeUnit.MILLISECONDS);
return new Duration(TimeUnit.MILLISECONDS, millis);
}
/** Adds the Caffeine eager expiration settings. */
public void addEagerExpiration() {
Config expiration = config.getConfig("policy.eager-expiration");
if (expiration.hasPath("after-write")) {
long nanos = expiration.getDuration("after-write", TimeUnit.NANOSECONDS);
configuration.setExpireAfterWrite(OptionalLong.of(nanos));
}
if (expiration.hasPath("after-access")) {
long nanos = expiration.getDuration("after-access", TimeUnit.NANOSECONDS);
configuration.setExpireAfterAccess(OptionalLong.of(nanos));
}
if (expiration.hasPath("variable")) {
configuration.setExpiryFactory(Optional.of(
FactoryBuilder.factoryOf(expiration.getString("variable"))));
}
}
/** Adds the Caffeine refresh settings. */
public void addRefresh() {
Config refresh = config.getConfig("policy.refresh");
if (refresh.hasPath("after-write")) {
long nanos = refresh.getDuration("after-write", TimeUnit.NANOSECONDS);
configuration.setRefreshAfterWrite(OptionalLong.of(nanos));
}
}
/** Adds the maximum size and weight bounding settings. */
private void addMaximum() {
Config maximum = config.getConfig("policy.maximum");
if (maximum.hasPath("size")) {
configuration.setMaximumSize(OptionalLong.of(maximum.getLong("size")));
}
if (maximum.hasPath("weight")) {
configuration.setMaximumWeight(OptionalLong.of(maximum.getLong("weight")));
}
if (maximum.hasPath("weigher")) {
configuration.setWeigherFactory(FactoryBuilder.factoryOf(maximum.getString("weigher")));
}
}
}
}