package org.ehcache.jsr107;
import org.ehcache.Status;
import org.ehcache.config.CacheConfiguration;
import org.ehcache.core.InternalCache;
import org.ehcache.core.spi.service.StatisticsService;
import org.ehcache.impl.config.copy.DefaultCopierConfiguration;
import org.ehcache.impl.copy.IdentityCopier;
import org.ehcache.jsr107.internal.Jsr107CacheLoaderWriter;
import org.ehcache.jsr107.internal.WrappedCacheLoaderWriter;
import org.ehcache.spi.loaderwriter.CacheLoaderWriter;
import org.ehcache.spi.service.ServiceConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.lang.management.ManagementFactory;
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 java.util.concurrent.ConcurrentMap;
import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheManager;
import javax.cache.configuration.Configuration;
import javax.cache.spi.CachingProvider;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer;
import static org.ehcache.jsr107.CloseUtil.chain;
import static org.ehcache.jsr107.CloseUtil.closeAll;
class Eh107CacheManager implements CacheManager {
private static final Logger LOG = LoggerFactory.getLogger(Eh107CacheManager.class);
private static final MBeanServer MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer();
private final Object cachesLock = new Object();
private final ConcurrentMap<String, Eh107Cache<?, ?>> caches = new ConcurrentHashMap<>();
private final org.ehcache.CacheManager ehCacheManager;
private final EhcacheCachingProvider cachingProvider;
private final ClassLoader classLoader;
private final URI uri;
private final Properties props;
private final ConfigurationMerger configurationMerger;
private final StatisticsService statisticsService;
Eh107CacheManager(EhcacheCachingProvider cachingProvider, org.ehcache.CacheManager ehCacheManager, Jsr107Service jsr107Service,
Properties props, ClassLoader classLoader, URI uri, ConfigurationMerger configurationMerger) {
this.cachingProvider = cachingProvider;
this.ehCacheManager = ehCacheManager;
this.props = props;
this.classLoader = classLoader;
this.uri = uri;
this.configurationMerger = configurationMerger;
this.statisticsService = jsr107Service.getStatistics();
refreshAllCaches();
}
private void refreshAllCaches() {
for (Map.Entry<String, CacheConfiguration<?, ?>> entry : ehCacheManager.getRuntimeConfiguration().getCacheConfigurations().entrySet()) {
String name = entry.getKey();
CacheConfiguration<?, ?> config = entry.getValue();
caches.putIfAbsent(name, wrapEhcacheCache(name, config));
}
for (Map.Entry<String, Eh107Cache<?, ?>> namedCacheEntry : caches.entrySet()) {
Eh107Cache<?, ?> cache = namedCacheEntry.getValue();
if (!cache.isClosed()) {
@SuppressWarnings("unchecked")
Eh107Configuration<?, ?> configuration = cache.getConfiguration(Eh107Configuration.class);
if (configuration.isManagementEnabled()) {
enableManagement(cache, true);
}
if (configuration.isStatisticsEnabled()) {
enableStatistics(cache, true);
}
}
}
}
private <K, V> Eh107Cache<K, V> wrapEhcacheCache(String alias, CacheConfiguration<K, V> ehConfig) {
org.ehcache.Cache<K, V> cache = ehCacheManager.getCache(alias, ehConfig.getKeyType(), ehConfig.getValueType());
return wrapEhcacheCache(alias, (InternalCache<K, V>)cache);
}
private <K, V> Eh107Cache<K, V> wrapEhcacheCache(String alias, InternalCache<K, V> cache) {
CacheLoaderWriter<? super K, V> cacheLoaderWriter = cache.getCacheLoaderWriter();
boolean storeByValueOnHeap = false;
for (ServiceConfiguration<?> serviceConfiguration : cache.getRuntimeConfiguration().getServiceConfigurations()) {
if (serviceConfiguration instanceof DefaultCopierConfiguration) {
DefaultCopierConfiguration<?> copierConfig = (DefaultCopierConfiguration) serviceConfiguration;
if(!copierConfig.getClazz().isAssignableFrom(IdentityCopier.class))
storeByValueOnHeap = true;
break;
}
}
Eh107Configuration<K, V> config = new Eh107ReverseConfiguration<>(cache, cacheLoaderWriter != null, cacheLoaderWriter != null, storeByValueOnHeap);
configurationMerger.setUpManagementAndStats(cache, config);
Eh107Expiry<K, V> expiry = new EhcacheExpiryWrapper<>(cache.getRuntimeConfiguration().getExpiryPolicy());
CacheResources<K, V> resources = new CacheResources<>(alias, wrapCacheLoaderWriter(cacheLoaderWriter), expiry);
return new Eh107Cache<>(alias, config, resources, cache, statisticsService, this);
}
private <K, V> Jsr107CacheLoaderWriter<K, V> wrapCacheLoaderWriter(CacheLoaderWriter<K, V> cacheLoaderWriter) {
return new WrappedCacheLoaderWriter<>(cacheLoaderWriter);
}
@Override
public CachingProvider getCachingProvider() {
return this.cachingProvider;
}
@Override
public URI getURI() {
return this.uri;
}
@Override
public ClassLoader getClassLoader() {
return this.classLoader;
}
@Override
public Properties getProperties() {
return new Properties(props);
}
@Override
public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(String cacheName, C config)
throws IllegalArgumentException {
checkClosed();
if (cacheName == null || config == null) {
throw new NullPointerException();
}
synchronized (cachesLock) {
if (config instanceof Eh107Configuration.Eh107ConfigurationWrapper) {
@SuppressWarnings("unchecked")
Eh107Configuration.Eh107ConfigurationWrapper<K, V> configurationWrapper = (Eh107Configuration.Eh107ConfigurationWrapper<K, V>)config;
CacheConfiguration<K, V> unwrap = configurationWrapper.getCacheConfiguration();
final org.ehcache.Cache<K, V> ehcache;
try {
ehcache = ehCacheManager.createCache(cacheName, unwrap);
} catch (IllegalArgumentException e) {
throw new CacheException("A Cache named [" + cacheName + "] already exists");
}
Eh107Cache<K, V> cache = wrapEhcacheCache(cacheName, (InternalCache<K, V>)ehcache);
assert safeCacheRetrieval(cacheName) == null;
caches.put(cacheName, cache);
@SuppressWarnings("unchecked")
Eh107Configuration<?, ?> configuration = cache.getConfiguration(Eh107Configuration.class);
if (configuration.isManagementEnabled()) {
enableManagement(cacheName, true);
}
if (configuration.isStatisticsEnabled()) {
enableStatistics(cacheName, true);
}
return cache;
}
ConfigurationMerger.ConfigHolder<K, V> configHolder = configurationMerger.mergeConfigurations(cacheName, config);
final InternalCache<K, V> ehCache;
try {
ehCache = (InternalCache<K, V>)ehCacheManager.createCache(cacheName, configHolder.cacheConfiguration);
} catch (IllegalArgumentException e) {
throw configHolder.cacheResources.closeResourcesAfter(new CacheException("A Cache named [" + cacheName + "] already exists"));
} catch (Throwable t) {
throw configHolder.cacheResources.closeResourcesAfter(new CacheException(t));
}
Eh107Cache<K, V> cache = null;
CacheResources<K, V> cacheResources = configHolder.cacheResources;
try {
if (configHolder.useEhcacheLoaderWriter) {
cacheResources = new CacheResources<>(cacheName, wrapCacheLoaderWriter(ehCache.getCacheLoaderWriter()),
cacheResources.getExpiryPolicy(), cacheResources.getListenerResources());
}
cache = new Eh107Cache<>(cacheName, new Eh107CompleteConfiguration<>(configHolder.jsr107Configuration, ehCache
.getRuntimeConfiguration()), cacheResources, ehCache, statisticsService, this);
caches.put(cacheName, cache);
if (configHolder.jsr107Configuration.isManagementEnabled()) {
enableManagement(cacheName, true);
}
if (configHolder.jsr107Configuration.isStatisticsEnabled()) {
enableStatistics(cacheName, true);
}
return cache;
} catch (Throwable t) {
if (cache != null) {
throw cache.closeInternalAfter(new CacheException(t));
} else {
throw cacheResources.closeResourcesAfter(new CacheException(t));
}
}
}
}
private void checkClosed() {
if (isClosed()) {
throw new IllegalStateException(this.toString() + " is closed");
}
}
@Override
public String toString() {
return getClass().getSimpleName() + "[" + uri + "]";
}
@SuppressWarnings("unchecked")
@Override
public <K, V> Cache<K, V> getCache(String cacheName, Class<K> keyType, Class<V> valueType) {
checkClosed();
if (cacheName == null || keyType == null || valueType == null) {
throw new NullPointerException();
}
Eh107Cache<K, V> cache = safeCacheRetrieval(cacheName);
if (cache == null) {
return null;
}
Class<?> actualKeyType = cache.getConfiguration(Configuration.class).getKeyType();
Class<?> actualValueType = cache.getConfiguration(Configuration.class).getValueType();
if (keyType != actualKeyType) {
throw new ClassCastException("Cache has key type " + actualKeyType.getName()
+ ", but getCache() called with key type " + keyType.getName());
}
if (valueType != actualValueType) {
throw new ClassCastException("Cache has value type " + actualValueType.getName()
+ ", but getCache() called with value type " + valueType.getName());
}
return cache;
}
@SuppressWarnings("unchecked")
@Override
public <K, V> Cache<K, V> getCache(String cacheName) {
checkClosed();
if (cacheName == null) {
throw new NullPointerException();
}
return safeCacheRetrieval(cacheName);
}
@SuppressWarnings("unchecked")
private <K, V> Eh107Cache<K, V> safeCacheRetrieval(final String cacheName) {
final Eh107Cache<?, ?> eh107Cache = caches.get(cacheName);
if(eh107Cache != null && eh107Cache.isClosed()) {
return null;
}
return (Eh107Cache<K, V>) eh107Cache;
}
@Override
public Iterable<String> getCacheNames() {
checkClosed();
refreshAllCaches();
return Collections.unmodifiableList(new ArrayList<>(caches.keySet()));
}
@Override
public void destroyCache(String cacheName) {
if (cacheName == null) {
throw new NullPointerException();
}
synchronized (cachesLock) {
checkClosed();
Eh107Cache<?, ?> cache = caches.remove(cacheName);
if (cache == null) {
return;
}
try {
chain(
() -> enableManagement(cache, false),
() -> enableStatistics(cache, false),
() -> cache.destroy(),
() -> ehCacheManager.removeCache(cache.getName())
);
} catch (Throwable t) {
throw new CacheException(t);
}
}
}
@Override
public void enableManagement(String cacheName, boolean enabled) {
checkClosed();
if (cacheName == null) {
throw new NullPointerException();
}
Eh107Cache<?, ?> cache = safeCacheRetrieval(cacheName);
if (cache == null) {
throw new IllegalArgumentException("No such Cache named " + cacheName);
}
enableManagement(cache, enabled);
}
private void enableManagement(Eh107Cache<?, ?> cache, boolean enabled) {
synchronized (cachesLock) {
checkClosed();
if (enabled) {
registerObject(cache.getManagementMBean());
} else {
unregisterObject(cache.getManagementMBean());
}
cache.setManagementEnabled(enabled);
}
}
private void unregisterObject(Eh107MXBean bean) {
try {
MBEAN_SERVER.unregisterMBean(bean.getObjectName());
} catch (InstanceNotFoundException e) {
} catch (Exception e) {
throw new CacheException(e);
}
}
private void registerObject(Eh107MXBean bean) {
try {
LOG.info("Registering Ehcache MBean {}", bean.getObjectName());
MBEAN_SERVER.registerMBean(bean, bean.getObjectName());
} catch (InstanceAlreadyExistsException e) {
} catch (Exception e) {
throw new CacheException(e);
}
}
@Override
public void enableStatistics(String cacheName, boolean enabled) {
checkClosed();
if (cacheName == null) {
throw new NullPointerException();
}
Eh107Cache<?, ?> cache = safeCacheRetrieval(cacheName);
if (cache == null) {
throw new IllegalArgumentException("No such Cache named " + cacheName);
}
enableStatistics(cache, enabled);
}
private void enableStatistics(Eh107Cache<?, ?> cache, boolean enabled) {
synchronized (cachesLock) {
checkClosed();
if (enabled) {
registerObject(cache.getStatisticsMBean());
} else {
unregisterObject(cache.getStatisticsMBean());
}
cache.setStatisticsEnabled(enabled);
}
}
@Override
public boolean isClosed() {
return ehCacheManager.getStatus() == Status.UNINITIALIZED;
}
@Override
public <T> T unwrap(Class<T> clazz) {
return Unwrap.unwrap(clazz, this, ehCacheManager);
}
@Override
public void close() {
cachingProvider.close(this);
}
void closeInternal() {
synchronized (cachesLock) {
try {
closeAll(caches.values(), (Closeable) caches::clear, ehCacheManager);
} catch (IOException e) {
throw new CacheException(e);
}
}
}
void close(Eh107Cache<?, ?> cache) {
if (caches.remove(cache.getName(), cache)) {
try {
chain(
() -> unregisterObject(cache.getManagementMBean()),
() -> unregisterObject(cache.getStatisticsMBean()),
() -> cache.closeInternal(),
() -> ehCacheManager.removeCache(cache.getName()));
} catch (Throwable t) {
throw new CacheException(t);
}
}
}
}