/* * 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; import static java.util.Objects.requireNonNull; import java.util.Optional; import java.util.concurrent.Executor; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import javax.cache.CacheManager; import javax.cache.configuration.CompleteConfiguration; import javax.cache.configuration.Configuration; import javax.cache.expiry.ExpiryPolicy; import javax.cache.integration.CacheLoader; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Ticker; import com.github.benmanes.caffeine.jcache.configuration.CaffeineConfiguration; import com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator; import com.github.benmanes.caffeine.jcache.event.EventDispatcher; import com.github.benmanes.caffeine.jcache.event.JCacheEvictionListener; import com.github.benmanes.caffeine.jcache.integration.JCacheLoaderAdapter; import com.github.benmanes.caffeine.jcache.management.JCacheStatisticsMXBean; import com.typesafe.config.Config; /** * A factory for creating a cache from the configuration. * * @author ben.manes@gmail.com (Ben Manes) */ final class CacheFactory { // Avoid asynchronous executions due to the TCK's poor test practices // https://github.com/jsr107/jsr107tck/issues/78 static final boolean USE_DIRECT_EXECUTOR = System.getProperties().containsKey("org.jsr107.tck.management.agentId"); final Config rootConfig; final CacheManager cacheManager; public CacheFactory(CacheManager cacheManager, Config rootConfig) { this.cacheManager = requireNonNull(cacheManager); this.rootConfig = requireNonNull(rootConfig); } /** * Returns a newly created cache instance if a definition is found in the external settings file. * * @param cacheName the name of the cache * @return a new cache instance or null if the named cache is not defined in the settings file */ public @Nullable <K, V> CacheProxy<K, V> tryToCreateFromExternalSettings(String cacheName) { Optional<CaffeineConfiguration<K, V>> configuration = TypesafeConfigurator.from(rootConfig, cacheName); return configuration.isPresent() ? createCache(cacheName, configuration.get()) : null; } /** * Returns a fully constructed cache based on the cache * * @param cacheName the name of the cache * @param configuration the full cache definition * @return a newly constructed cache instance */ public <K, V> CacheProxy<K, V> createCache(String cacheName, Configuration<K, V> configuration) { CaffeineConfiguration<K, V> config = resolveConfigurationFor(configuration); return new Builder<>(cacheName, config).build(); } /** Copies the configuration and overlays it on top of the default settings. */ private <K, V> CaffeineConfiguration<K, V> resolveConfigurationFor( Configuration<K, V> configuration) { if (configuration instanceof CaffeineConfiguration<?, ?>) { return new CaffeineConfiguration<>((CaffeineConfiguration<K, V>) configuration); } CaffeineConfiguration<K, V> defaults = TypesafeConfigurator.defaults(rootConfig); if (configuration instanceof CompleteConfiguration<?, ?>) { CaffeineConfiguration<K, V> config = new CaffeineConfiguration<>( (CompleteConfiguration<K, V>) configuration); config.setCopierFactory(defaults.getCopierFactory()); return config; } defaults.setTypes(configuration.getKeyType(), configuration.getValueType()); defaults.setStoreByValue(configuration.isStoreByValue()); return defaults; } /** A one-shot builder for creating a cache instance. */ private final class Builder<K, V> { final Ticker ticker; final String cacheName; final Executor executor; final ExpiryPolicy expiry; final EventDispatcher<K, V> dispatcher; final JCacheStatisticsMXBean statistics; final Caffeine<Object, Object> caffeine; final CaffeineConfiguration<K, V> config; CacheLoader<K, V> cacheLoader; JCacheEvictionListener<K, V> evictionListener; Builder(String cacheName, CaffeineConfiguration<K, V> config) { this.config = config; this.cacheName = cacheName; this.caffeine = Caffeine.newBuilder(); this.statistics = new JCacheStatisticsMXBean(); this.ticker = config.getTickerFactory().create(); this.expiry = config.getExpiryPolicyFactory().create(); this.executor = USE_DIRECT_EXECUTOR ? Runnable::run : ForkJoinPool.commonPool(); this.dispatcher = new EventDispatcher<>(executor); caffeine.executor(executor); if (config.getCacheLoaderFactory() != null) { cacheLoader = config.getCacheLoaderFactory().create(); } config.getCacheEntryListenerConfigurations().forEach(dispatcher::register); } /** Creates a configured cache. */ public CacheProxy<K, V> build() { boolean evicts = configureMaximumSize() || configureMaximumWeight() || configureExpireAfterWrite() || configureExpireAfterAccess() || configureExpireVariably(); if (evicts) { configureEvictionListener(); } CacheProxy<K, V> cache; if (isReadThrough()) { configureRefreshAfterWrite(); cache = newLoadingCacheProxy(); } else { cache = newCacheProxy(); } if (evicts) { evictionListener.setCache(cache); } return cache; } /** Determines if the cache should operate in read through mode. */ private boolean isReadThrough() { return config.isReadThrough() && (cacheLoader != null); } /** Creates a cache that does not read through on a cache miss. */ private CacheProxy<K, V> newCacheProxy() { return new CacheProxy<>(cacheName, executor, cacheManager, config, caffeine.build(), dispatcher, Optional.ofNullable(cacheLoader), expiry, ticker, statistics); } /** Creates a cache that reads through on a cache miss. */ private CacheProxy<K, V> newLoadingCacheProxy() { JCacheLoaderAdapter<K, V> adapter = new JCacheLoaderAdapter<>( cacheLoader, dispatcher, expiry, ticker, statistics); CacheProxy<K, V> cache = new LoadingCacheProxy<>(cacheName, executor, cacheManager, config, caffeine.build(adapter), dispatcher, cacheLoader, expiry, ticker, statistics); adapter.setCache(cache); return cache; } /** Configures the maximum size and returns if set. */ private boolean configureMaximumSize() { if (config.getMaximumSize().isPresent()) { caffeine.maximumSize(config.getMaximumSize().getAsLong()); } return config.getMaximumSize().isPresent(); } /** Configures the maximum weight and returns if set. */ private boolean configureMaximumWeight() { if (config.getMaximumWeight().isPresent()) { caffeine.maximumWeight(config.getMaximumWeight().getAsLong()); caffeine.weigher(config.getWeigherFactory().create()); } return config.getMaximumWeight().isPresent(); } /** Configures the write expiration and returns if set. */ private boolean configureExpireAfterWrite() { if (config.getExpireAfterWrite().isPresent()) { caffeine.expireAfterWrite(config.getExpireAfterWrite().getAsLong(), TimeUnit.NANOSECONDS); } return config.getExpireAfterWrite().isPresent(); } /** Configures the access expiration and returns if set. */ private boolean configureExpireAfterAccess() { if (config.getExpireAfterAccess().isPresent()) { caffeine.expireAfterAccess(config.getExpireAfterAccess().getAsLong(), TimeUnit.NANOSECONDS); } return config.getExpireAfterAccess().isPresent(); } /** Configures the write expiration and returns if set. */ private boolean configureExpireVariably() { config.getExpiryFactory().ifPresent(factory -> caffeine.expireAfter(factory.create())); return config.getExpireAfterWrite().isPresent(); } private void configureRefreshAfterWrite() { if (config.getRefreshAfterWrite().isPresent()) { caffeine.refreshAfterWrite(config.getRefreshAfterWrite().getAsLong(), TimeUnit.NANOSECONDS); } } /** Configures the removal listener. */ private void configureEvictionListener() { evictionListener = new JCacheEvictionListener<>(dispatcher, statistics); caffeine.writer(evictionListener); } } }