JCSCachingManager.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.commons.jcs3.jcache;
import static org.apache.commons.jcs3.jcache.Asserts.assertNotNull;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
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.CacheManager;
import javax.cache.configuration.Configuration;
import javax.cache.spi.CachingProvider;
import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
import org.apache.commons.jcs3.engine.control.CompositeCache;
import org.apache.commons.jcs3.engine.control.CompositeCacheConfigurator;
import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
import org.apache.commons.jcs3.jcache.lang.Subsitutor;
import org.apache.commons.jcs3.jcache.proxy.ClassLoaderAwareCache;
public class JCSCachingManager implements CacheManager
{
private static final Subsitutor SUBSTITUTOR = Subsitutor.Helper.INSTANCE;
private static final String DEFAULT_CONFIG =
"jcs.default=DC\n" +
"jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes\n" +
"jcs.default.cacheattributes.MaxObjects=200001\n" +
"jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache\n" +
"jcs.default.cacheattributes.UseMemoryShrinker=true\n" +
"jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600\n" +
"jcs.default.cacheattributes.ShrinkerIntervalSeconds=60\n" +
"jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes\n" +
"jcs.default.elementattributes.IsEternal=false\n" +
"jcs.default.elementattributes.MaxLife=700\n" +
"jcs.default.elementattributes.IdleTime=1800\n" +
"jcs.default.elementattributes.IsSpool=true\n" +
"jcs.default.elementattributes.IsRemote=true\n" +
"jcs.default.elementattributes.IsLateral=true\n";
private static class InternalManager extends CompositeCacheManager
{
protected static InternalManager create()
{
return new InternalManager();
}
@Override
protected CompositeCacheConfigurator newConfigurator()
{
return new CompositeCacheConfigurator()
{
@Override
protected <K, V> CompositeCache<K, V> newCache(
final ICompositeCacheAttributes cca, final IElementAttributes ea)
{
return new ExpiryAwareCache<>( cca, ea );
}
};
}
@Override // needed to call it from JCSCachingManager
protected void initialize() {
super.initialize();
}
}
private final CachingProvider provider;
private final URI uri;
private final ClassLoader loader;
private final Properties properties;
private final ConcurrentMap<String, Cache<?, ?>> caches = new ConcurrentHashMap<>();
private final Properties configProperties;
private volatile boolean closed;
private final InternalManager delegate = InternalManager.create();
public JCSCachingManager(final CachingProvider provider, final URI uri, final ClassLoader loader, final Properties properties)
{
this.provider = provider;
this.uri = uri;
this.loader = loader;
this.properties = readConfig(uri, loader, properties);
this.configProperties = properties;
delegate.setJmxName(CompositeCacheManager.JMX_OBJECT_NAME
+ ",provider=" + provider.hashCode()
+ ",uri=" + uri.toString().replaceAll(",|:|=|\n", ".")
+ ",classloader=" + loader.hashCode()
+ ",properties=" + this.properties.hashCode());
delegate.initialize();
delegate.configure(this.properties);
}
private static Properties readConfig(final URI uri, final ClassLoader loader, final Properties properties) {
final Properties props = new Properties();
try {
if (JCSCachingProvider.DEFAULT_URI.toString().equals(uri.toString()) || uri.toURL().getProtocol().equals("jcs"))
{
final Enumeration<URL> resources = loader.getResources(uri.getPath());
if (!resources.hasMoreElements()) // default
{
props.load(new ByteArrayInputStream(DEFAULT_CONFIG.getBytes(StandardCharsets.UTF_8)));
}
else
{
do
{
addProperties(resources.nextElement(), props);
}
while (resources.hasMoreElements());
}
}
else
{
props.load(uri.toURL().openStream());
}
} catch (final IOException e) {
throw new IllegalStateException(e);
}
if (properties != null)
{
props.putAll(properties);
}
for (final Map.Entry<Object, Object> entry : props.entrySet()) {
if (entry.getValue() == null)
{
continue;
}
final String substitute = SUBSTITUTOR.substitute(entry.getValue().toString());
if (!substitute.equals(entry.getValue()))
{
entry.setValue(substitute);
}
}
return props;
}
private static void addProperties(final URL url, final Properties aggregator)
{
try (InputStream inStream = url.openStream()) {
aggregator.load(inStream);
} catch (final IOException e) {
throw new IllegalArgumentException(e);
}
}
private void assertNotClosed()
{
if (isClosed())
{
throw new IllegalStateException("cache manager closed");
}
}
@Override
// TODO: use configuration + handle not serializable key/values
public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(final String cacheName, final C configuration)
throws IllegalArgumentException
{
assertNotClosed();
assertNotNull(cacheName, "cacheName");
assertNotNull(configuration, "configuration");
final Class<K> keyType = configuration.getKeyType();
final Class<V> valueType = configuration.getValueType();
if (caches.containsKey(cacheName)) {
throw new javax.cache.CacheException("cache " + cacheName + " already exists");
}
@SuppressWarnings("unchecked")
final Cache<K, V> cache = ClassLoaderAwareCache.wrap(loader,
new JCSCache<>(
loader, this, cacheName,
new JCSConfiguration<K, V>(configuration, keyType, valueType),
properties,
ExpiryAwareCache.class.cast(delegate.getCache(cacheName))));
caches.putIfAbsent(cacheName, cache);
return getCache(cacheName, keyType, valueType);
}
@Override
public void destroyCache(final String cacheName)
{
assertNotClosed();
assertNotNull(cacheName, "cacheName");
final Cache<?, ?> cache = caches.remove(cacheName);
if (cache != null && !cache.isClosed())
{
cache.clear();
cache.close();
}
}
@Override
public void enableManagement(final String cacheName, final boolean enabled)
{
assertNotClosed();
assertNotNull(cacheName, "cacheName");
final JCSCache<?, ?> cache = getJCSCache(cacheName);
if (cache != null)
{
if (enabled)
{
cache.enableManagement();
}
else
{
cache.disableManagement();
}
}
}
private JCSCache<?, ?> getJCSCache(final String cacheName)
{
final Cache<?, ?> cache = caches.get(cacheName);
return JCSCache.class.cast(ClassLoaderAwareCache.getDelegate(cache));
}
@Override
public void enableStatistics(final String cacheName, final boolean enabled)
{
assertNotClosed();
assertNotNull(cacheName, "cacheName");
final JCSCache<?, ?> cache = getJCSCache(cacheName);
if (cache != null)
{
if (enabled)
{
cache.enableStatistics();
}
else
{
cache.disableStatistics();
}
}
}
@Override
public synchronized void close()
{
if (isClosed())
{
return;
}
assertNotClosed();
for (final Cache<?, ?> c : caches.values())
{
c.close();
}
caches.clear();
closed = true;
if (JCSCachingProvider.class.isInstance(provider))
{
JCSCachingProvider.class.cast(provider).remove(this);
}
delegate.shutDown();
}
@Override
public <T> T unwrap(final Class<T> clazz)
{
if (clazz.isInstance(this))
{
return clazz.cast(this);
}
throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
}
@Override
public boolean isClosed()
{
return closed;
}
@Override
public <K, V> Cache<K, V> getCache(final String cacheName)
{
assertNotClosed();
assertNotNull(cacheName, "cacheName");
return (Cache<K, V>) doGetCache(cacheName, Object.class, Object.class);
}
@Override
public Iterable<String> getCacheNames()
{
return new ImmutableIterable<>(caches.keySet());
}
@Override
public <K, V> Cache<K, V> getCache(final String cacheName, final Class<K> keyType, final Class<V> valueType)
{
assertNotClosed();
assertNotNull(cacheName, "cacheName");
assertNotNull(keyType, "keyType");
assertNotNull(valueType, "valueType");
try
{
return doGetCache(cacheName, keyType, valueType);
}
catch (final IllegalArgumentException iae)
{
throw new ClassCastException(iae.getMessage());
}
}
private <K, V> Cache<K, V> doGetCache(final String cacheName, final Class<K> keyType, final Class<V> valueType)
{
@SuppressWarnings("unchecked") // common map for all caches
final Cache<K, V> cache = (Cache<K, V>) caches.get(cacheName);
if (cache == null)
{
return null;
}
@SuppressWarnings("unchecked") // don't know how to solve this
final Configuration<K, V> config = cache.getConfiguration(Configuration.class);
if (keyType != null && !config.getKeyType().isAssignableFrom(keyType) ||
valueType != null && !config.getValueType().isAssignableFrom(valueType))
{
throw new IllegalArgumentException("this cache is <" + config.getKeyType().getName() + ", " + config.getValueType().getName()
+ "> " + " and not <" + keyType.getName() + ", " + valueType.getName() + ">");
}
return cache;
}
@Override
public CachingProvider getCachingProvider()
{
return provider;
}
@Override
public URI getURI()
{
return uri;
}
@Override
public ClassLoader getClassLoader()
{
return loader;
}
@Override
public Properties getProperties()
{
return configProperties;
}
public void release(final String name) {
caches.remove(name);
}
}