blob: feb52d73b692b48886a964298ed6e9460bd51223 [file] [log] [blame]
/*
* 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.geronimo.jcache.simple;
import static org.apache.geronimo.jcache.simple.Asserts.assertNotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheManager;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.configuration.Configuration;
import javax.cache.configuration.Factory;
import javax.cache.event.CacheEntryEvent;
import javax.cache.event.EventType;
import javax.cache.expiry.Duration;
import javax.cache.expiry.EternalExpiryPolicy;
import javax.cache.expiry.ExpiryPolicy;
import javax.cache.integration.CacheLoader;
import javax.cache.integration.CacheLoaderException;
import javax.cache.integration.CacheWriter;
import javax.cache.integration.CacheWriterException;
import javax.cache.integration.CompletionListener;
import javax.cache.processor.EntryProcessor;
import javax.cache.processor.EntryProcessorException;
import javax.cache.processor.EntryProcessorResult;
import javax.management.ObjectName;
public class SimpleCache<K, V> implements Cache<K, V> {
private final SimpleManager manager;
private final SimpleConfiguration<K, V> config;
private final CacheLoader<K, V> loader;
private final CacheWriter<? super K, ? super V> writer;
private final ExpiryPolicy expiryPolicy;
private final ObjectName cacheConfigObjectName;
private final ObjectName cacheStatsObjectName;
private final String name;
private final ConcurrentHashMap<SimpleKey<K>, SimpleElement<V>> delegate;
private final Map<CacheEntryListenerConfiguration<K, V>, SimpleListener<K, V>> listeners = new ConcurrentHashMap<>();
private final Statistics statistics = new Statistics();
private final ExecutorService pool;
private final Serializations serializations;
private final Collection<Future<?>> poolTasks = new CopyOnWriteArraySet<>();
private volatile boolean closed = false;
public SimpleCache(final ClassLoader classLoader, final SimpleManager mgr, final String cacheName,
final SimpleConfiguration<K, V> configuration, final Properties properties,
final ExecutorService executorService) {
manager = mgr;
name = cacheName;
final int capacity = Integer.parseInt(property(properties, cacheName, "capacity", "1000"));
final float loadFactor = Float.parseFloat(property(properties, cacheName, "loadFactor", "0.75"));
final int concurrencyLevel = Integer.parseInt(property(properties, cacheName, "concurrencyLevel", "16"));
delegate = new ConcurrentHashMap<>(capacity, loadFactor, concurrencyLevel);
config = configuration;
pool = executorService;
final long evictionPause = Long.parseLong(
properties.getProperty(cacheName + ".evictionPause", properties.getProperty("evictionPause", "30000")));
if (evictionPause > 0) {
final long maxDeleteByEvictionRun = Long.parseLong(property(properties, cacheName, "maxDeleteByEvictionRun", "100"));
addPoolTask(new EvictionThread(evictionPause, maxDeleteByEvictionRun));
}
serializations = new Serializations(property(properties, cacheName, "serialization.whitelist", null));
final Factory<CacheLoader<K, V>> cacheLoaderFactory = configuration.getCacheLoaderFactory();
if (cacheLoaderFactory == null) {
loader = NoLoader.INSTANCE;
} else {
loader = ExceptionWrapperHandler.newProxy(classLoader, cacheLoaderFactory.create(), CacheLoaderException.class,
CacheLoader.class);
}
final Factory<CacheWriter<? super K, ? super V>> cacheWriterFactory = configuration.getCacheWriterFactory();
if (cacheWriterFactory == null) {
writer = NoWriter.INSTANCE;
} else {
writer = ExceptionWrapperHandler.newProxy(classLoader, cacheWriterFactory.create(), CacheWriterException.class,
CacheWriter.class);
}
final Factory<ExpiryPolicy> expiryPolicyFactory = configuration.getExpiryPolicyFactory();
if (expiryPolicyFactory == null) {
expiryPolicy = new EternalExpiryPolicy();
} else {
expiryPolicy = expiryPolicyFactory.create();
}
for (final CacheEntryListenerConfiguration<K, V> listener : config.getCacheEntryListenerConfigurations()) {
listeners.put(listener, new SimpleListener<>(listener));
}
statistics.setActive(config.isStatisticsEnabled());
final String mgrStr = manager.getURI().toString().replaceAll(",|:|=|\n", ".");
final String cacheStr = name.replaceAll(",|:|=|\n", ".");
try {
cacheConfigObjectName = new ObjectName(
"javax.cache:type=CacheConfiguration," + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
cacheStatsObjectName = new ObjectName(
"javax.cache:type=CacheStatistics," + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
} catch (final Exception e) {
throw new IllegalArgumentException(e);
}
if (config.isManagementEnabled()) {
JMXs.register(cacheConfigObjectName, new SimpleCacheMXBean<K, V>(this));
}
if (config.isStatisticsEnabled()) {
JMXs.register(cacheStatsObjectName, new SimpleCacheStatisticsMXBean(statistics));
}
}
private void assertNotClosed() {
if (isClosed()) {
throw new IllegalStateException("cache closed");
}
}
@Override
public V get(final K key) {
assertNotClosed();
assertNotNull(key, "key");
final long getStart = Times.now(false);
return doGetControllingExpiry(getStart, key, true, false, false, true, loader);
}
private V doLoad(final K key, final boolean update, final boolean propagateLoadException, final CacheLoader<K, V> loader) {
V v = null;
try {
v = loader.load(key);
} catch (final CacheLoaderException e) {
if (propagateLoadException) {
throw e;
}
}
if (v != null) {
final Duration duration = update ? expiryPolicy.getExpiryForUpdate() : expiryPolicy.getExpiryForCreation();
if (isNotZero(duration)) {
delegate.put(new SimpleKey<>(key), new SimpleElement<>(v, duration));
}
}
return v;
}
private void touch(final SimpleKey<K> key, final SimpleElement<V> element) {
if (config.isStoreByValue()) {
delegate.put(new SimpleKey<>(serializations.copy(manager.getClassLoader(), key.getKey())), element);
}
}
@Override
public Map<K, V> getAll(final Set<? extends K> keys) {
assertNotClosed();
for (final K k : keys) {
assertNotNull(k, "key");
}
final Map<K, V> result = new HashMap<>();
for (final K key : keys) {
assertNotNull(key, "key");
final SimpleKey<K> simpleKey = new SimpleKey<>(key);
final SimpleElement<V> elt = delegate.get(simpleKey);
V val = elt != null ? elt.getElement() : null;
if (val == null && config.isReadThrough()) {
val = doLoad(key, false, false, loader);
if (val != null) {
result.put(key, val);
}
} else if (elt != null) {
final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
if (isNotZero(expiryForAccess)) {
touch(simpleKey, elt);
result.put(key, val);
} else {
expires(simpleKey);
}
}
}
return result;
}
@Override
public boolean containsKey(final K key) {
assertNotClosed();
assertNotNull(key, "key");
return delegate.get(new SimpleKey<>(key)) != null;
}
@Override
public void put(final K key, final V rawValue) {
assertNotClosed();
assertNotNull(key, "key");
assertNotNull(rawValue, "value");
final boolean storeByValue = config.isStoreByValue();
final SimpleKey<K> simpleKey = new SimpleKey<>(storeByValue ? serializations.copy(manager.getClassLoader(), key) : key);
final SimpleElement<V> oldElt = delegate.get(simpleKey);
final V old = oldElt != null ? oldElt.getElement() : null;
final V value = storeByValue ? serializations.copy(manager.getClassLoader(), rawValue) : rawValue;
final boolean created = old == null;
final Duration duration = created ? expiryPolicy.getExpiryForCreation() : expiryPolicy.getExpiryForUpdate();
if (isNotZero(duration)) {
final boolean statisticsEnabled = config.isStatisticsEnabled();
final long start = Times.now(false);
writer.write(new SimpleEntry<>(key, value));
delegate.put(simpleKey, new SimpleElement<>(value, duration));
if (!listeners.isEmpty()) {
for (final SimpleListener<K, V> listener : listeners.values()) {
if (created) {
listener.onCreated(Collections.<CacheEntryEvent<? extends K, ? extends V>> singletonList(
new SimpleEvent<>(this, EventType.CREATED, null, key, value)));
} else
listener.onUpdated(Collections.<CacheEntryEvent<? extends K, ? extends V>> singletonList(
new SimpleEvent<>(this, EventType.UPDATED, old, key, value)));
}
}
if (statisticsEnabled) {
statistics.increasePuts(1);
statistics.addPutTime(Times.now(false) - start);
}
} else {
if (!created) {
expires(simpleKey);
}
}
}
private void expires(final SimpleKey<K> cacheKey) {
final SimpleElement<V> elt = delegate.get(cacheKey);
delegate.remove(cacheKey);
onExpired(cacheKey, elt);
}
private void onExpired(final SimpleKey<K> cacheKey, final SimpleElement<V> elt) {
for (final SimpleListener<K, V> listener : listeners.values()) {
listener.onExpired(Collections.<CacheEntryEvent<? extends K, ? extends V>> singletonList(
new SimpleEvent<>(this, EventType.REMOVED, null, cacheKey.getKey(), elt.getElement())));
}
}
@Override
public V getAndPut(final K key, final V value) {
assertNotClosed();
assertNotNull(key, "key");
assertNotNull(value, "value");
final long getStart = Times.now(false);
final V v = doGetControllingExpiry(getStart, key, false, false, true, false, loader);
put(key, value);
return v;
}
@Override
public void putAll(final Map<? extends K, ? extends V> map) {
assertNotClosed();
final TempStateCacheView<K, V> view = new TempStateCacheView<K, V>(this);
for (final Map.Entry<? extends K, ? extends V> e : map.entrySet()) {
view.put(e.getKey(), e.getValue());
}
view.merge();
}
@Override
public boolean putIfAbsent(final K key, final V value) {
final boolean statisticsEnabled = config.isStatisticsEnabled();
if (!containsKey(key)) {
if (statisticsEnabled) {
statistics.increaseMisses(1);
}
put(key, value);
return true;
} else {
if (statisticsEnabled) {
statistics.increaseHits(1);
}
}
return false;
}
@Override
public boolean remove(final K key) {
assertNotClosed();
assertNotNull(key, "key");
final boolean statisticsEnabled = config.isStatisticsEnabled();
final long start = Times.now(!statisticsEnabled);
writer.delete(key);
final SimpleKey<K> cacheKey = new SimpleKey<>(key);
final SimpleElement<V> v = delegate.remove(cacheKey);
if (v == null || v.isExpired()) {
return false;
}
final V value = v.getElement();
for (final SimpleListener<K, V> listener : listeners.values()) {
listener.onRemoved(Collections.<CacheEntryEvent<? extends K, ? extends V>> singletonList(
new SimpleEvent<>(this, EventType.REMOVED, value, key, value)));
}
if (statisticsEnabled) {
statistics.increaseRemovals(1);
statistics.addRemoveTime(Times.now(false) - start);
}
return true;
}
@Override
public boolean remove(final K key, final V oldValue) {
assertNotClosed();
assertNotNull(key, "key");
assertNotNull(oldValue, "oldValue");
final long getStart = Times.now(false);
final V v = doGetControllingExpiry(getStart, key, false, false, false, false, loader);
if (oldValue.equals(v)) {
remove(key);
return true;
} else if (v != null) {
// weird but just for stats to be right
// (org.jsr107.tck.expiry.CacheExpiryTest.removeSpecifiedEntryShouldNotCallExpiryPolicyMethods())
expiryPolicy.getExpiryForAccess();
}
return false;
}
@Override
public V getAndRemove(final K key) {
assertNotClosed();
assertNotNull(key, "key");
final long getStart = Times.now(false);
final V v = doGetControllingExpiry(getStart, key, false, false, true, false, loader);
remove(key);
return v;
}
private V doGetControllingExpiry(final long getStart, final K key, final boolean updateAcess, final boolean forceDoLoad,
final boolean skipLoad, final boolean propagateLoadException, final CacheLoader<K, V> loader) {
final boolean statisticsEnabled = config.isStatisticsEnabled();
final SimpleKey<K> simpleKey = new SimpleKey<>(key);
final SimpleElement<V> elt = delegate.get(simpleKey);
V v = elt != null ? elt.getElement() : null;
if (v == null && (config.isReadThrough() || forceDoLoad)) {
if (!skipLoad) {
v = doLoad(key, false, propagateLoadException, loader);
}
} else if (statisticsEnabled) {
if (v != null) {
statistics.increaseHits(1);
} else {
statistics.increaseMisses(1);
}
}
if (updateAcess && elt != null) {
final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
if (!isNotZero(expiryForAccess)) {
expires(simpleKey);
}
}
if (statisticsEnabled && v != null) {
statistics.addGetTime(Times.now(false) - getStart);
}
return v;
}
@Override
public boolean replace(final K key, final V oldValue, final V newValue) {
assertNotClosed();
assertNotNull(key, "key");
assertNotNull(oldValue, "oldValue");
assertNotNull(newValue, "newValue");
final V value = doGetControllingExpiry(Times.now(config.isStatisticsEnabled()), key, false, config.isReadThrough(), false,
true, loader);
if (value != null && value.equals(oldValue)) {
put(key, newValue);
return true;
} else if (value != null) {
expiryPolicy.getExpiryForAccess();
}
return false;
}
@Override
public boolean replace(final K key, final V value) {
assertNotClosed();
assertNotNull(key, "key");
assertNotNull(value, "value");
boolean statisticsEnabled = config.isStatisticsEnabled();
if (containsKey(key)) {
if (statisticsEnabled) {
statistics.increaseHits(1);
}
put(key, value);
return true;
} else if (statisticsEnabled) {
statistics.increaseMisses(1);
}
return false;
}
@Override
public V getAndReplace(final K key, final V value) {
assertNotClosed();
assertNotNull(key, "key");
assertNotNull(value, "value");
final boolean statisticsEnabled = config.isStatisticsEnabled();
final SimpleElement<V> elt = delegate.get(new SimpleKey<>(key));
if (elt != null) {
V oldValue = elt.getElement();
if (oldValue == null && config.isReadThrough()) {
oldValue = doLoad(key, false, false, loader);
} else if (statisticsEnabled) {
statistics.increaseHits(1);
}
put(key, value);
return oldValue;
} else if (statisticsEnabled) {
statistics.increaseMisses(1);
}
return null;
}
@Override
public void removeAll(final Set<? extends K> keys) {
assertNotClosed();
assertNotNull(keys, "keys");
for (final K k : keys) {
remove(k);
}
}
@Override
public void removeAll() {
assertNotClosed();
for (final SimpleKey<K> k : delegate.keySet()) {
remove(k.getKey());
}
}
@Override
public void clear() {
assertNotClosed();
delegate.clear();
}
@Override
public <C2 extends Configuration<K, V>> C2 getConfiguration(final Class<C2> clazz) {
assertNotClosed();
return clazz.cast(config);
}
@Override
public void loadAll(final Set<? extends K> keys, final boolean replaceExistingValues,
final CompletionListener completionListener) {
assertNotClosed();
assertNotNull(keys, "keys");
if (loader == null) { // quick exit path
if (completionListener != null) {
completionListener.onCompletion();
}
return;
}
for (final K k : keys) {
assertNotNull(k, "a key");
}
addPoolTask(new Runnable() {
@Override
public void run() {
doLoadAll(keys, replaceExistingValues, completionListener);
}
});
}
private void addPoolTask(final Runnable runnable) {
final AtomicReference<Future<?>> ref = new AtomicReference<>();
final CountDownLatch refIsSet = new CountDownLatch(1);
ref.set(pool.submit(new Runnable() {
@Override
public void run() {
try {
runnable.run();
} finally {
try {
refIsSet.await();
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
poolTasks.remove(ref.get());
}
}
}));
refIsSet.countDown();
poolTasks.add(ref.get());
}
private void doLoadAll(final Set<? extends K> keys, final boolean replaceExistingValues,
final CompletionListener completionListener) {
try {
final long now = Times.now(false);
final Map<K, V> kvMap = loader.loadAll(keys);
if (kvMap == null) {
return;
}
final CacheLoader<K, V> preloaded = new MapLoader<>(kvMap);
for (final K k : keys) {
if (replaceExistingValues) {
doLoad(k, containsKey(k), completionListener != null, preloaded);
} else if (!containsKey(k)) {
doGetControllingExpiry(now, k, true, true, false, completionListener != null, preloaded);
}
}
} catch (final RuntimeException e) {
if (completionListener != null) {
completionListener.onException(e);
return;
}
}
if (completionListener != null) {
completionListener.onCompletion();
}
}
@Override
public <T> T invoke(final K key, final EntryProcessor<K, V, T> entryProcessor, final Object... arguments)
throws EntryProcessorException {
final TempStateCacheView<K, V> view = new TempStateCacheView<K, V>(this);
final T t = doInvoke(view, key, entryProcessor, arguments);
view.merge();
return t;
}
private <T> T doInvoke(final TempStateCacheView<K, V> view, final K key, final EntryProcessor<K, V, T> entryProcessor,
final Object... arguments) {
assertNotClosed();
assertNotNull(entryProcessor, "entryProcessor");
assertNotNull(key, "key");
try {
if (config.isStatisticsEnabled()) {
if (containsKey(key)) {
statistics.increaseHits(1);
} else {
statistics.increaseMisses(1);
}
}
return entryProcessor.process(new SimpleMutableEntry<>(view, key), arguments);
} catch (final Exception ex) {
return throwEntryProcessorException(ex);
}
}
@Override
public <T> Map<K, EntryProcessorResult<T>> invokeAll(final Set<? extends K> keys,
final EntryProcessor<K, V, T> entryProcessor, final Object... arguments) {
assertNotClosed();
assertNotNull(entryProcessor, "entryProcessor");
final Map<K, EntryProcessorResult<T>> results = new HashMap<>();
for (final K k : keys) {
try {
final T invoke = invoke(k, entryProcessor, arguments);
if (invoke != null) {
results.put(k, new EntryProcessorResult<T>() {
@Override
public T get() throws EntryProcessorException {
return invoke;
}
});
}
} catch (final Exception e) {
results.put(k, new EntryProcessorResult<T>() {
@Override
public T get() throws EntryProcessorException {
return throwEntryProcessorException(e);
}
});
}
}
return results;
}
@Override
public void registerCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
assertNotClosed();
if (listeners.containsKey(cacheEntryListenerConfiguration)) {
throw new IllegalArgumentException(cacheEntryListenerConfiguration + " already registered");
}
listeners.put(cacheEntryListenerConfiguration, new SimpleListener<>(cacheEntryListenerConfiguration));
config.addListener(cacheEntryListenerConfiguration);
}
@Override
public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
assertNotClosed();
listeners.remove(cacheEntryListenerConfiguration);
config.removeListener(cacheEntryListenerConfiguration);
}
@Override
public Iterator<Entry<K, V>> iterator() {
assertNotClosed();
final Iterator<SimpleKey<K>> keys = new HashSet<>(delegate.keySet()).iterator();
return new Iterator<Entry<K, V>>() {
private K lastKey = null;
@Override
public boolean hasNext() {
return keys.hasNext();
}
@Override
public Entry<K, V> next() {
lastKey = keys.next().getKey();
return new SimpleEntry<>(lastKey, get(lastKey));
}
@Override
public void remove() {
if (isClosed() || lastKey == null) {
throw new IllegalStateException(isClosed() ? "cache closed" : "call next() before remove()");
}
SimpleCache.this.remove(lastKey);
}
};
}
@Override
public String getName() {
assertNotClosed();
return name;
}
@Override
public CacheManager getCacheManager() {
assertNotClosed();
return manager;
}
@Override
public synchronized void close() {
if (isClosed()) {
return;
}
for (final Future<?> task : poolTasks) {
task.cancel(true);
}
final CacheException ce = new CacheException();
manager.release(getName());
closed = true;
close(loader, ce);
close(writer, ce);
close(expiryPolicy, ce);
for (final SimpleListener<K, V> listener : listeners.values()) {
try {
listener.close();
} catch (final Exception e) {
ce.addSuppressed(e);
}
}
listeners.clear();
JMXs.unregister(cacheConfigObjectName);
JMXs.unregister(cacheStatsObjectName);
delegate.clear();
if (ce.getSuppressed().length > 0) {
throw ce;
}
}
@Override
public boolean isClosed() {
return closed;
}
@Override
public <T> T unwrap(final Class<T> clazz) {
assertNotClosed();
if (clazz.isInstance(this)) {
return clazz.cast(this);
}
if (clazz.isAssignableFrom(Map.class) || clazz.isAssignableFrom(ConcurrentMap.class)) {
return clazz.cast(delegate);
}
throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
}
public Statistics getStatistics() {
return statistics;
}
public void enableManagement() {
config.managementEnabled();
JMXs.register(cacheConfigObjectName, new SimpleCacheMXBean<K, V>(this));
}
public void disableManagement() {
config.managementDisabled();
JMXs.unregister(cacheConfigObjectName);
}
public void enableStatistics() {
config.statisticsEnabled();
statistics.setActive(true);
JMXs.register(cacheStatsObjectName, new SimpleCacheStatisticsMXBean(statistics));
}
public void disableStatistics() {
config.statisticsDisabled();
statistics.setActive(false);
JMXs.unregister(cacheStatsObjectName);
}
private static String property(final Properties properties, final String cacheName, final String name,
final String defaultValue) {
return properties.getProperty(cacheName + "." + name, properties.getProperty(name, defaultValue));
}
private static boolean isNotZero(final Duration duration) {
return duration == null || !duration.isZero();
}
private static <T> T throwEntryProcessorException(final Exception ex) {
if (EntryProcessorException.class.isInstance(ex)) {
throw EntryProcessorException.class.cast(ex);
}
throw new EntryProcessorException(ex);
}
private static void close(final Object potentiallyCloseable, final CacheException wrapper) {
if (AutoCloseable.class.isInstance(potentiallyCloseable)) {
try {
AutoCloseable.class.cast(potentiallyCloseable).close();
} catch (final Exception re) {
wrapper.addSuppressed(re);
}
}
}
private class EvictionThread implements Runnable {
private final long pause;
private final long maxDelete;
private EvictionThread(final long evictionPause, final long maxDelete) {
this.pause = evictionPause;
this.maxDelete = maxDelete;
}
@Override
public void run() {
while (!isClosed()) {
try {
Thread.sleep(pause);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
if (delegate.isEmpty()) {
continue;
}
try {
final List<SimpleKey<K>> keys = new ArrayList<>(delegate.keySet());
Collections.sort(keys, new Comparator<SimpleKey<K>>() {
@Override
public int compare(final SimpleKey<K> o1, final SimpleKey<K> o2) {
final long l = o1.lastAccess() - o2.lastAccess();
if (l == 0) {
return keys.indexOf(o1) - keys.indexOf(o2);
}
return (int) l;
}
});
int delete = 0;
for (final SimpleKey<K> key : keys) {
final SimpleElement<V> elt = delegate.get(key);
if (elt != null && elt.isExpired()) {
delegate.remove(key);
statistics.increaseEvictions(1);
onExpired(key, elt);
delete++;
if (delete >= maxDelete) {
break;
}
}
}
} catch (final Exception e) {
// no-op
}
}
}
}
private static class MapLoader<K, V> implements CacheLoader<K, V> {
private final Map<K, V> loaded;
private MapLoader(final Map<K, V> loaded) {
this.loaded = loaded;
}
@Override
public V load(final K key) throws CacheLoaderException {
return loaded.get(key);
}
@Override
public Map<K, V> loadAll(final Iterable<? extends K> keys) throws CacheLoaderException {
throw new UnsupportedOperationException();
}
}
}