/* * 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.lang.ref.WeakReference; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; import javax.cache.Cache; import javax.cache.CacheException; import javax.cache.CacheManager; import javax.cache.configuration.Configuration; import javax.cache.spi.CachingProvider; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; /** * An implementation of JSR-107 {@link CacheManager} that manages Caffeine-based caches. * * @author ben.manes@gmail.com (Ben Manes) */ public final class CacheManagerImpl implements CacheManager { private final WeakReference<ClassLoader> classLoaderReference; private final Map<String, CacheProxy<?, ?>> caches; private final CachingProvider cacheProvider; private final CacheFactory cacheFactory; private final Properties properties; private final URI uri; private volatile boolean closed; public CacheManagerImpl(CachingProvider cacheProvider, URI uri, ClassLoader classLoader, Properties properties) { this(cacheProvider, uri, classLoader, properties, ConfigFactory.load()); } public CacheManagerImpl(CachingProvider cacheProvider, URI uri, ClassLoader classLoader, Properties properties, Config rootConfig) { this.classLoaderReference = new WeakReference<>(requireNonNull(classLoader)); this.cacheFactory = new CacheFactory(this, rootConfig); this.cacheProvider = requireNonNull(cacheProvider); this.properties = requireNonNull(properties); this.caches = new ConcurrentHashMap<>(); this.uri = requireNonNull(uri); } @Override public CachingProvider getCachingProvider() { return cacheProvider; } @Override public URI getURI() { return uri; } @Override public ClassLoader getClassLoader() { return classLoaderReference.get(); } @Override public Properties getProperties() { return properties; } @Override public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(String cacheName, C configuration) throws IllegalArgumentException { requireNotClosed(); requireNonNull(configuration); CacheProxy<?, ?> cache = caches.compute(cacheName, (name, existing) -> { if ((existing != null) && !existing.isClosed()) { throw new CacheException("Cache " + cacheName + " already exists"); } return cacheFactory.createCache(cacheName, configuration); }); enableManagement(cache.getName(), cache.getConfiguration().isManagementEnabled()); enableStatistics(cache.getName(), cache.getConfiguration().isStatisticsEnabled()); @SuppressWarnings("unchecked") Cache<K, V> castedCache = (Cache<K, V>) cache; return castedCache; } @Override public <K, V> Cache<K, V> getCache(String cacheName, Class<K> keyType, Class<V> valueType) { CacheProxy<K, V> cache = getOrCreateCache(cacheName); if (cache == null) { return null; } requireNonNull(keyType); requireNonNull(valueType); Configuration<?, ?> config = cache.getConfiguration(); if (keyType != config.getKeyType()) { throw new ClassCastException("Incompatible cache key types specified, expected " + config.getKeyType() + " but " + keyType + " was specified"); } else if (valueType != config.getValueType()) { throw new ClassCastException("Incompatible cache value types specified, expected " + config.getValueType() + " but " + valueType + " was specified"); } return cache; } @Override public <K, V> Cache<K, V> getCache(String cacheName) { CacheProxy<K, V> cache = getOrCreateCache(cacheName); if (cache == null) { return null; } Configuration<?, ?> configuration = cache.getConfiguration(); if (!Object.class.equals(configuration.getKeyType()) || !Object.class.equals(configuration.getValueType())) { String msg = String.format("Cache %s was defined with specific types Cache<%s, %s> in which " + "case CacheManager.getCache(String, Class, Class) must be used", cacheName, configuration.getKeyType(), configuration.getValueType()); throw new IllegalArgumentException(msg); } return cache; } /** Returns the cache, creating it if necessary. */ private @Nullable <K, V> CacheProxy<K, V> getOrCreateCache(String cacheName) { requireNonNull(cacheName); requireNotClosed(); CacheProxy<?, ?> cache = caches.computeIfAbsent(cacheName, name -> { CacheProxy<?, ?> created = cacheFactory.tryToCreateFromExternalSettings(name); if (created != null) { created.enableManagement(created.getConfiguration().isManagementEnabled()); created.enableStatistics(created.getConfiguration().isStatisticsEnabled()); } return created; }); @SuppressWarnings("unchecked") CacheProxy<K, V> castedCache = (CacheProxy<K, V>) cache; return castedCache; } @Override public Iterable<String> getCacheNames() { return Collections.unmodifiableCollection(new ArrayList<>(caches.keySet())); } @Override public void destroyCache(String cacheName) { requireNotClosed(); Cache<?, ?> cache = caches.remove(cacheName); if (cache != null) { cache.close(); } } @Override public void enableManagement(String cacheName, boolean enabled) { requireNotClosed(); CacheProxy<?, ?> cache = caches.get(cacheName); if (cache == null) { return; } cache.enableManagement(enabled); } @Override public void enableStatistics(String cacheName, boolean enabled) { requireNotClosed(); CacheProxy<?, ?> cache = caches.get(cacheName); if (cache == null) { return; } cache.enableStatistics(enabled); } @Override public void close() { if (isClosed()) { return; } synchronized (cacheFactory) { if (!isClosed()) { cacheProvider.close(uri, classLoaderReference.get()); for (Cache<?, ?> cache : caches.values()) { cache.close(); } closed = true; } } } @Override public boolean isClosed() { return closed; } @Override public <T> T unwrap(Class<T> clazz) { if (clazz.isAssignableFrom(getClass())) { return clazz.cast(this); } throw new IllegalArgumentException("Unwapping to " + clazz + " is not a supported by this implementation"); } /** Checks that the cache manager is not closed. */ private void requireNotClosed() { if (isClosed()) { throw new IllegalStateException(); } } }