
org.trimou.jdk8.cache.MapBackedComputingCacheFactory Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2014 Martin Kouba
*
* 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 org.trimou.jdk8.cache;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.trimou.engine.cache.ComputingCache;
import org.trimou.engine.cache.ComputingCache.Function;
import org.trimou.engine.cache.ComputingCache.Listener;
import org.trimou.engine.cache.ComputingCacheFactory;
import org.trimou.engine.config.AbstractConfigurationAware;
import org.trimou.util.Checker;
import com.google.common.collect.ImmutableMap;
/**
* A computing cache factory producing computing cache implementations backed by
* {@link ConcurrentHashMap}. This implementation is a bit faster than the
* default one using {@link com.google.common.cache.LoadingCache}. On the other
* hand it does not support automatic timeout eviction and listeners. Moreover
* its size-based eviction is not so effective.
*
* @author Martin Kouba
* @see Map#computeIfAbsent(Object, java.util.function.Function)
*/
public class MapBackedComputingCacheFactory extends AbstractConfigurationAware
implements ComputingCacheFactory {
private static final Logger logger = LoggerFactory
.getLogger(MapBackedComputingCacheFactory.class);
private final MaxSizeStrategy maxSizeStrategy;
public MapBackedComputingCacheFactory() {
this(MaxSizeStrategy.CLEAR);
}
/**
*
* @param maxSizeStrategy
*/
public MapBackedComputingCacheFactory(MaxSizeStrategy maxSizeStrategy) {
Checker.checkArgumentNotNull(maxSizeStrategy);
this.maxSizeStrategy = maxSizeStrategy;
}
@Override
public ComputingCache create(String consumerId,
Function computingFunction, Long expirationTimeout,
Long maxSize, Listener listener) {
if (expirationTimeout != null) {
throw new IllegalArgumentException(
"Expiration timeout not supported");
}
if (listener != null) {
logger.warn("Listener not supported - notifications will not be delivered");
}
if (maxSize != null && !maxSizeStrategy.isEvictionSupported()) {
throw new IllegalArgumentException(
"Max size limit not supported - use a different eviction strategy");
}
return new ConcurrentHashMapAdapter(
new ConcurrentHashMap(), computingFunction, maxSize,
maxSizeStrategy);
}
/**
*
* @author Martin Kouba
*
* @param
* @param
*/
private static class ConcurrentHashMapAdapter implements
ComputingCache {
private static final Logger logger = LoggerFactory
.getLogger(ConcurrentHashMapAdapter.class);
private final MaxSizeStrategy maxSizeStrategy;
private final Long maxSize;
private final ConcurrentHashMap map;
private final FunctionAdapter computingFunctionAdapter;
/**
*
* @param map
* @param computingFunction
* @param maxSize
* @param maxSizeStrategy
*/
ConcurrentHashMapAdapter(ConcurrentHashMap map,
ComputingCache.Function computingFunction, Long maxSize,
MaxSizeStrategy maxSizeStrategy) {
this.map = map;
this.maxSize = maxSize;
this.computingFunctionAdapter = new FunctionAdapter(
computingFunction, this);
this.maxSizeStrategy = maxSizeStrategy;
}
@Override
public V get(K key) {
try {
return compute(key);
} catch (MaxSizeExceededException e) {
handleMaxSizeExceeding();
// Theoretically, this may also throw MaxSizeExceededException
// if the limit is exceeded before the value is computed, which
// is unlikely.
return compute(key);
}
}
@Override
public V getIfPresent(K key) {
return map.get(key);
}
@Override
public void clear() {
map.clear();
}
@Override
public long size() {
return map.size();
}
@Override
public void invalidate(ComputingCache.KeyPredicate keyPredicate) {
for (Iterator iterator = map.keySet().iterator(); iterator
.hasNext();) {
if (keyPredicate.apply(iterator.next())) {
iterator.remove();
}
}
}
@Override
public Map getAllPresent() {
return ImmutableMap. copyOf(map);
}
private V compute(K key) {
return map.computeIfAbsent(key, computingFunctionAdapter);
}
private synchronized void handleMaxSizeExceeding() {
if (map.size() > maxSize) {
applyMaxSizeStrategy();
}
}
private void applyMaxSizeStrategy() {
switch (maxSizeStrategy) {
case CLEAR:
// Clearing the map is not quite elegant, but exceeding the
// limit should be an exteme situation
logger.debug(
"Max size limit of {} exceeded - removing all entries from the cache",
maxSize);
map.clear();
break;
default:
logger.warn(
"Max size limit of {} exceeded but the eviction strategy {} is not implemented!",
maxSize, maxSizeStrategy);
break;
}
}
}
/**
*
* @author Martin Kouba
*
* @param
* @param
*/
private static class FunctionAdapter implements
java.util.function.Function {
private final Function computingFunction;
private final ConcurrentHashMapAdapter mapAdapter;
/**
*
* @param computingFunction
* @param adapter
*/
public FunctionAdapter(Function computingFunction,
ConcurrentHashMapAdapter adapter) {
this.computingFunction = computingFunction;
this.mapAdapter = adapter;
}
@Override
public V apply(K key) {
// Note that computation must not attempt to update any other
// mappings of the map - therefore we cannot perform eviction here
if (mapAdapter.maxSize != null
&& mapAdapter.map.size() > mapAdapter.maxSize) {
throw new MaxSizeExceededException();
}
return computingFunction.compute(key);
}
}
/**
* Defines the strategy applied when the max size limit is set and exceeded.
*
* @author Martin Kouba
*/
public static enum MaxSizeStrategy {
/**
* Do nothing
*/
NOOP(false),
/**
* Remove all entries from the cache
*/
CLEAR(true), ;
private MaxSizeStrategy(boolean isEvictionSupported) {
this.isEvictionSupported = isEvictionSupported;
}
private boolean isEvictionSupported;
public boolean isEvictionSupported() {
return isEvictionSupported;
}
}
/**
* Signals that the max size limit was exceeded.
*
* @author Martin Kouba
*/
private static class MaxSizeExceededException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy