blob: 67c9d03d908c1ee9fa5347d6db8909bb16061d48 [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.ofbiz.base.util.cache;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import jdbm.helper.FastIterator;
import jdbm.htree.HTree;
import org.ofbiz.base.concurrent.ExecutionPool;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.ObjectType;
import org.ofbiz.base.util.UtilGenerics;
import org.ofbiz.base.util.UtilObject;
import org.ofbiz.base.util.UtilValidate;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap.Builder;
import com.googlecode.concurrentlinkedhashmap.EvictionListener;
/**
* Generalized caching utility. Provides a number of caching features:
* <ul>
* <li>Limited or unlimited element capacity
* <li>If limited, removes elements with the LRU (Least Recently Used) algorithm
* <li>Keeps track of when each element was loaded into the cache
* <li>Using the expireTime can report whether a given element has expired
* <li>Counts misses and hits
* </ul>
*
*/
@SuppressWarnings("serial")
public class UtilCache<K, V> implements Serializable, EvictionListener<Object, CacheLine<V>> {
public static final String module = UtilCache.class.getName();
/** A static Map to keep track of all of the UtilCache instances. */
private static final ConcurrentHashMap<String, UtilCache<?, ?>> utilCacheTable = new ConcurrentHashMap<String, UtilCache<?, ?>>();
/** An index number appended to utilCacheTable names when there are conflicts. */
private final static ConcurrentHashMap<String, AtomicInteger> defaultIndices = new ConcurrentHashMap<String, AtomicInteger>();
/** The name of the UtilCache instance, is also the key for the instance in utilCacheTable. */
private final String name;
/** A count of the number of cache hits */
protected AtomicLong hitCount = new AtomicLong(0);
/** A count of the number of cache misses because it is not found in the cache */
protected AtomicLong missCountNotFound = new AtomicLong(0);
/** A count of the number of cache misses because it expired */
protected AtomicLong missCountExpired = new AtomicLong(0);
/** A count of the number of cache misses because it was cleared from the Soft Reference (ie garbage collection, etc) */
protected AtomicLong missCountSoftRef = new AtomicLong(0);
/** A count of the number of cache hits on removes */
protected AtomicLong removeHitCount = new AtomicLong(0);
/** A count of the number of cache misses on removes */
protected AtomicLong removeMissCount = new AtomicLong(0);
/** The maximum number of elements in the cache.
* If set to 0, there will be no limit on the number of elements in the cache.
*/
protected int sizeLimit = 0;
protected int maxInMemory = 0;
/** Specifies the amount of time since initial loading before an element will be reported as expired.
* If set to 0, elements will never expire.
*/
protected long expireTimeNanos = 0;
/** Specifies whether or not to use soft references for this cache, defaults to false */
protected boolean useSoftReference = false;
/** Specifies whether or not to use file base stored for this cache, defaults to false */
protected boolean useFileSystemStore = false;
private String fileStore = "runtime/data/utilcache";
/** The set of listeners to receive notifications when items are modified (either deliberately or because they were expired). */
protected Set<CacheListener<K, V>> listeners = new CopyOnWriteArraySet<CacheListener<K, V>>();
protected transient HTree<Object, V> fileTable = null;
protected ConcurrentMap<Object, CacheLine<V>> memoryTable = null;
protected JdbmRecordManager jdbmMgr;
// weak ref on this
private static final ConcurrentMap<String, JdbmRecordManager> fileManagers = new ConcurrentHashMap<String, JdbmRecordManager>();
/** Constructor which specifies the cacheName as well as the sizeLimit, expireTime and useSoftReference.
* The passed sizeLimit, expireTime and useSoftReference will be overridden by values from cache.properties if found.
* @param sizeLimit The sizeLimit member is set to this value
* @param expireTime The expireTime member is set to this value
* @param cacheName The name of the cache.
* @param useSoftReference Specifies whether or not to use soft references for this cache.
*/
private UtilCache(String cacheName, int sizeLimit, int maxInMemory, long expireTimeMillis, boolean useSoftReference, boolean useFileSystemStore, String propName, String... propNames) {
this.name = cacheName;
this.sizeLimit = sizeLimit;
this.maxInMemory = maxInMemory;
this.expireTimeNanos = TimeUnit.NANOSECONDS.convert(expireTimeMillis, TimeUnit.MILLISECONDS);
this.useSoftReference = useSoftReference;
this.useFileSystemStore = useFileSystemStore;
setPropertiesParams(propName);
setPropertiesParams(propNames);
int maxMemSize = this.maxInMemory;
if (maxMemSize == 0) maxMemSize = sizeLimit;
if (maxMemSize == 0) {
memoryTable = new ConcurrentHashMap<Object, CacheLine<V>>();
} else {
memoryTable = new Builder<Object, CacheLine<V>>()
.maximumWeightedCapacity(maxMemSize)
.listener(this)
.build();
}
if (this.useFileSystemStore) {
// create the manager the first time it is needed
jdbmMgr = fileManagers.get(fileStore);
if (jdbmMgr == null) {
Debug.logImportant("Creating file system cache store for cache with name: " + cacheName, module);
try {
String ofbizHome = System.getProperty("ofbiz.home");
if (ofbizHome == null) {
Debug.logError("No ofbiz.home property set in environment", module);
} else {
jdbmMgr = new JdbmRecordManager(ofbizHome + "/" + fileStore);
}
} catch (IOException e) {
Debug.logError(e, "Error creating file system cache store for cache with name: " + cacheName, module);
}
fileManagers.putIfAbsent(fileStore, jdbmMgr);
}
jdbmMgr = fileManagers.get(fileStore);
if (jdbmMgr != null) {
try {
this.fileTable = HTree.createInstance(jdbmMgr);
jdbmMgr.setNamedObject(cacheName, this.fileTable.getRecid());
jdbmMgr.commit();
} catch (IOException e) {
Debug.logError(e, module);
}
}
}
}
private static String getNextDefaultIndex(String cacheName) {
AtomicInteger curInd = defaultIndices.get(cacheName);
if (curInd == null) {
defaultIndices.putIfAbsent(cacheName, new AtomicInteger(0));
curInd = defaultIndices.get(cacheName);
}
int i = curInd.getAndIncrement();
return i == 0 ? "" : Integer.toString(i);
}
public static String getPropertyParam(ResourceBundle res, String[] propNames, String parameter) {
try {
for (String propName : propNames) {
String key = propName.concat(".").concat(parameter);
if (res.containsKey(key)) {
try {
return res.getString(key);
} catch (MissingResourceException e) {
}
}
}
} catch (Exception e) {
Debug.logWarning(e, "Error getting " + parameter + " value from ResourceBundle for propNames: " + propNames, module);
}
return null;
}
protected void setPropertiesParams(String cacheName) {
setPropertiesParams(new String[] {cacheName});
}
public void setPropertiesParams(String[] propNames) {
setPropertiesParams("cache", propNames);
}
public void setPropertiesParams(String settingsResourceName, String[] propNames) {
ResourceBundle res = ResourceBundle.getBundle(settingsResourceName);
if (res != null) {
String value = getPropertyParam(res, propNames, "maxSize");
if (UtilValidate.isNotEmpty(value)) {
this.sizeLimit = Integer.parseInt(value);
}
value = getPropertyParam(res, propNames, "maxInMemory");
if (UtilValidate.isNotEmpty(value)) {
this.maxInMemory = Integer.parseInt(value);
}
value = getPropertyParam(res, propNames, "expireTime");
if (UtilValidate.isNotEmpty(value)) {
this.expireTimeNanos = TimeUnit.NANOSECONDS.convert(Long.parseLong(value), TimeUnit.MILLISECONDS);
}
value = getPropertyParam(res, propNames, "useSoftReference");
if (value != null) {
useSoftReference = "true".equals(value);
}
value = getPropertyParam(res, propNames, "useFileSystemStore");
if (value != null) {
useFileSystemStore = "true".equals(value);
}
value = getPropertyParam(res, new String[0], "cache.file.store");
if (value != null) {
fileStore = value;
}
}
}
private Object fromKey(Object key) {
return key == null ? ObjectType.NULL : key;
}
@SuppressWarnings("unchecked")
private K toKey(Object key) {
return key == ObjectType.NULL ? null : (K) key;
}
private void addAllFileTableKeys(Set<Object> keys) throws IOException {
FastIterator<Object> iter = fileTable.keys();
Object key = null;
while ((key = iter.next()) != null) {
keys.add(key);
}
}
public Object getCacheLineTable() {
throw new UnsupportedOperationException();
}
public boolean isEmpty() {
if (fileTable != null) {
try {
synchronized (this) {
return fileTable.keys().next() == null;
}
} catch (IOException e) {
Debug.logError(e, module);
return false;
}
} else {
return memoryTable.isEmpty();
}
}
/** Puts or loads the passed element into the cache
* @param key The key for the element, used to reference it in the hashtables and LRU linked list
* @param value The value of the element
*/
public V put(K key, V value) {
return putInternal(key, value, expireTimeNanos);
}
public V putIfAbsent(K key, V value) {
return putIfAbsentInternal(key, value, expireTimeNanos);
}
public V putIfAbsentAndGet(K key, V value) {
V cachedValue = putIfAbsent(key, value);
return (cachedValue != null? cachedValue: value);
}
CacheLine<V> createSoftRefCacheLine(final Object key, V value, long loadTimeNanos, long expireTimeNanos) {
return tryRegister(loadTimeNanos, new SoftRefCacheLine<V>(value, loadTimeNanos, expireTimeNanos) {
@Override
CacheLine<V> changeLine(boolean useSoftReference, long expireTimeNanos) {
if (useSoftReference) {
if (differentExpireTime(expireTimeNanos)) {
return this;
} else {
return createSoftRefCacheLine(key, getValue(), loadTimeNanos, expireTimeNanos);
}
} else {
return createHardRefCacheLine(key, getValue(), loadTimeNanos, expireTimeNanos);
}
}
@Override
void remove() {
removeInternal(key, this);
}
});
}
CacheLine<V> createHardRefCacheLine(final Object key, V value, long loadTimeNanos, long expireTimeNanos) {
return tryRegister(loadTimeNanos, new HardRefCacheLine<V>(value, loadTimeNanos, expireTimeNanos) {
@Override
CacheLine<V> changeLine(boolean useSoftReference, long expireTimeNanos) {
if (useSoftReference) {
return createSoftRefCacheLine(key, getValue(), loadTimeNanos, expireTimeNanos);
} else {
if (differentExpireTime(expireTimeNanos)) {
return this;
} else {
return createHardRefCacheLine(key, getValue(), loadTimeNanos, expireTimeNanos);
}
}
}
@Override
void remove() {
removeInternal(key, this);
}
});
}
private CacheLine<V> tryRegister(long loadTimeNanos, CacheLine<V> line) {
if (loadTimeNanos > 0) {
ExecutionPool.addPulse(line);
}
return line;
}
private CacheLine<V> createCacheLine(K key, V value, long expireTimeNanos) {
long loadTimeNanos = expireTimeNanos > 0 ? System.nanoTime() : 0;
if (useSoftReference) {
return createSoftRefCacheLine(key, value, loadTimeNanos, expireTimeNanos);
} else {
return createHardRefCacheLine(key, value, loadTimeNanos, expireTimeNanos);
}
}
private V cancel(CacheLine<V> line) {
// FIXME: this is a race condition, the item could expire
// between the time it is replaced, and it is cancelled
V oldValue = line.getValue();
ExecutionPool.removePulse(line);
line.cancel();
return oldValue;
}
/** Puts or loads the passed element into the cache
* @param key The key for the element, used to reference it in the hashtables and LRU linked list
* @param value The value of the element
* @param expireTimeMillis how long to keep this key in the cache
*/
public V put(K key, V value, long expireTimeMillis) {
return putInternal(key, value, TimeUnit.NANOSECONDS.convert(expireTimeMillis, TimeUnit.MILLISECONDS));
}
public V putIfAbsent(K key, V value, long expireTimeMillis) {
return putIfAbsentInternal(key, value, TimeUnit.NANOSECONDS.convert(expireTimeMillis, TimeUnit.MILLISECONDS));
}
V putInternal(K key, V value, long expireTimeNanos) {
Object nulledKey = fromKey(key);
CacheLine<V> oldCacheLine = memoryTable.put(nulledKey, createCacheLine(key, value, expireTimeNanos));
V oldValue = oldCacheLine == null ? null : cancel(oldCacheLine);
if (fileTable != null) {
try {
synchronized (this) {
if (oldValue == null) oldValue = fileTable.get(nulledKey);
fileTable.put(nulledKey, value);
jdbmMgr.commit();
}
} catch (IOException e) {
Debug.logError(e, module);
}
}
if (oldValue == null) {
noteAddition(key, value);
return null;
} else {
noteUpdate(key, value, oldValue);
return oldValue;
}
}
V putIfAbsentInternal(K key, V value, long expireTimeNanos) {
Object nulledKey = fromKey(key);
V oldValue;
if (fileTable != null) {
try {
synchronized (this) {
oldValue = fileTable.get(nulledKey);
if (oldValue == null) {
memoryTable.put(nulledKey, createCacheLine(key, value, expireTimeNanos));
fileTable.put(nulledKey, value);
jdbmMgr.commit();
}
}
} catch (IOException e) {
Debug.logError(e, module);
oldValue = null;
}
} else {
CacheLine<V> newCacheLine = createCacheLine(key, value, expireTimeNanos);
CacheLine<V> oldCacheLine = memoryTable.putIfAbsent(nulledKey, newCacheLine);
if (oldCacheLine == null) {
oldValue = null;
} else {
oldValue = oldCacheLine.getValue();
cancel(newCacheLine);
}
}
if (oldValue == null) {
noteAddition(key, value);
return null;
} else {
return oldValue;
}
}
/** Gets an element from the cache according to the specified key.
* @param key The key for the element, used to reference it in the hashtables and LRU linked list
* @return The value of the element specified by the key
*/
public V get(Object key) {
boolean countGet = true;
Object nulledKey = fromKey(key);
CacheLine<V> line = memoryTable.get(nulledKey);
if (line == null) {
if (fileTable != null) {
V value;
try {
synchronized (this) {
value = fileTable.get(nulledKey);
}
} catch (IOException e) {
Debug.logError(e, module);
value = null;
}
if (value == null) {
missCountNotFound.incrementAndGet();
return null;
} else {
hitCount.incrementAndGet();
}
memoryTable.put(nulledKey, createCacheLine(UtilGenerics.<K>cast(key), value, expireTimeNanos));
return value;
} else {
missCountNotFound.incrementAndGet();
}
} else {
if (countGet) hitCount.incrementAndGet();
}
return line != null ? line.getValue() : null;
}
public Collection<V> values() {
if (fileTable != null) {
List<V> values = new LinkedList<V>();
try {
synchronized (this) {
FastIterator<V> iter = fileTable.values();
V value = iter.next();
while (value != null) {
values.add(value);
value = iter.next();
}
}
} catch (IOException e) {
Debug.logError(e, module);
}
return values;
} else {
List<V> valuesList = new LinkedList<V>();
for (CacheLine<V> line: memoryTable.values()) {
valuesList.add(line.getValue());
}
return valuesList;
}
}
private long findSizeInBytes(Object o) {
try {
if (o == null) {
if (Debug.infoOn()) Debug.logInfo("Found null object in cache: " + getName(), module);
return 0;
}
if (o instanceof Serializable) {
return UtilObject.getByteCount(o);
} else {
if (Debug.infoOn()) Debug.logInfo("Unable to compute memory size for non serializable object; returning 0 byte size for object of " + o.getClass(), module);
return 0;
}
} catch (NotSerializableException e) {
// this happens when we try to get the byte count for an object which itself is
// serializable, but fails to be serialized, such as a map holding unserializable objects
if (Debug.warningOn()) {
Debug.logWarning("NotSerializableException while computing memory size; returning 0 byte size for object of " + e.getMessage(), module);
}
return 0;
} catch (Exception e) {
Debug.logWarning(e, "Unable to compute memory size for object of " + o.getClass(), module);
return 0;
}
}
public long getSizeInBytes() {
long totalSize = 0;
if (fileTable != null) {
try {
synchronized (this) {
FastIterator<V> iter = fileTable.values();
V value = iter.next();
while (value != null) {
totalSize += findSizeInBytes(value);
value = iter.next();
}
}
} catch (IOException e) {
Debug.logError(e, module);
return 0;
}
} else {
for (CacheLine<V> line: memoryTable.values()) {
totalSize += findSizeInBytes(line.getValue());
}
}
return totalSize;
}
/** Removes an element from the cache according to the specified key
* @param key The key for the element, used to reference it in the hashtables and LRU linked list
* @return The value of the removed element specified by the key
*/
public V remove(Object key) {
return this.removeInternal(key, true);
}
/** This is used for internal remove calls because we only want to count external calls */
@SuppressWarnings("unchecked")
protected synchronized V removeInternal(Object key, boolean countRemove) {
if (key == null) {
if (Debug.verboseOn()) Debug.logVerbose("In UtilCache tried to remove with null key, using NullObject" + this.name, module);
}
Object nulledKey = fromKey(key);
CacheLine<V> oldCacheLine;
V oldValue;
if (fileTable != null) {
try {
synchronized (this) {
try {
oldValue = fileTable.get(nulledKey);
} catch (IOException e) {
oldValue = null;
throw e;
}
fileTable.remove(nulledKey);
jdbmMgr.commit();
}
} catch (IOException e) {
oldValue = null;
Debug.logError(e, module);
}
oldCacheLine = memoryTable.remove(nulledKey);
} else {
oldCacheLine = memoryTable.remove(nulledKey);
oldValue = oldCacheLine != null ? oldCacheLine.getValue() : null;
}
if (oldCacheLine != null) {
cancel(oldCacheLine);
}
if (oldValue != null) {
noteRemoval((K) key, oldValue);
if (countRemove) removeHitCount.incrementAndGet();
return oldValue;
} else {
if (countRemove) removeMissCount.incrementAndGet();
return null;
}
}
protected synchronized void removeInternal(Object key, CacheLine<V> existingCacheLine) {
Object nulledKey = fromKey(key);
cancel(existingCacheLine);
if (!memoryTable.remove(nulledKey, existingCacheLine)) {
return;
}
if (fileTable != null) {
try {
synchronized (this) {
fileTable.remove(nulledKey);
jdbmMgr.commit();
}
} catch (IOException e) {
Debug.logError(e, module);
}
}
noteRemoval(UtilGenerics.<K>cast(key), existingCacheLine.getValue());
}
/** Removes all elements from this cache */
public synchronized void erase() {
if (fileTable != null) {
// FIXME: erase from memory too
synchronized (this) {
Set<Object> keys = new HashSet<Object>();
try {
addAllFileTableKeys(keys);
} catch (IOException e) {
Debug.logError(e, module);
}
for (Object key: keys) {
try {
V value = fileTable.get(key);
noteRemoval(toKey(key), value);
removeHitCount.incrementAndGet();
fileTable.remove(key);
jdbmMgr.commit();
} catch (IOException e) {
Debug.logError(e, module);
}
}
}
memoryTable.clear();
} else {
Iterator<Map.Entry<Object, CacheLine<V>>> it = memoryTable.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Object, CacheLine<V>> entry = it.next();
noteRemoval(toKey(entry.getKey()), entry.getValue().getValue());
removeHitCount.incrementAndGet();
it.remove();
}
}
}
public void clear() {
erase();
clearCounters();
}
/** Removes all elements from this cache */
public static void clearAllCaches() {
// We make a copy since clear may take time
for (UtilCache<?,?> cache : utilCacheTable.values()) {
cache.clear();
}
}
public static Set<String> getUtilCacheTableKeySet() {
Set<String> set = new HashSet<String>(utilCacheTable.size());
set.addAll(utilCacheTable.keySet());
return set;
}
/** Getter for the name of the UtilCache instance.
* @return The name of the instance
*/
public String getName() {
return this.name;
}
/** Returns the number of successful hits on the cache
* @return The number of successful cache hits
*/
public long getHitCount() {
return this.hitCount.get();
}
/** Returns the number of cache misses from entries that are not found in the cache
* @return The number of cache misses
*/
public long getMissCountNotFound() {
return this.missCountNotFound.get();
}
/** Returns the number of cache misses from entries that are expired
* @return The number of cache misses
*/
public long getMissCountExpired() {
return this.missCountExpired.get();
}
/** Returns the number of cache misses from entries that are have had the soft reference cleared out (by garbage collector and such)
* @return The number of cache misses
*/
public long getMissCountSoftRef() {
return this.missCountSoftRef.get();
}
/** Returns the number of cache misses caused by any reason
* @return The number of cache misses
*/
public long getMissCountTotal() {
return getMissCountSoftRef() + getMissCountNotFound() + getMissCountExpired();
}
public long getRemoveHitCount() {
return this.removeHitCount.get();
}
public long getRemoveMissCount() {
return this.removeMissCount.get();
}
/** Clears the hit and miss counters
*/
public void clearCounters() {
this.hitCount.set(0);
this.missCountNotFound.set(0);
this.missCountExpired.set(0);
this.missCountSoftRef.set(0);
this.removeHitCount.set(0);
this.removeMissCount.set(0);
}
public void setMaxInMemory(int newInMemory) {
this.maxInMemory = newInMemory;
Map<Object, CacheLine<V>> oldmap = this.memoryTable;
if (newInMemory > 0) {
if (this.memoryTable instanceof ConcurrentLinkedHashMap<?, ?>) {
((ConcurrentLinkedHashMap<?, ?>) this.memoryTable).setCapacity(newInMemory);
return;
} else {
this.memoryTable =new Builder<Object, CacheLine<V>>()
.maximumWeightedCapacity(newInMemory)
.build();
}
} else {
this.memoryTable = new ConcurrentHashMap<Object, CacheLine<V>>();
}
this.memoryTable.putAll(oldmap);
}
public int getMaxInMemory() {
return maxInMemory;
}
public void setSizeLimit(int newSizeLimit) {
this.sizeLimit = newSizeLimit;
}
public int getSizeLimit() {
return sizeLimit;
}
/** Sets the expire time for the cache elements.
* If 0, elements never expire.
* @param expireTimeMillis The expire time for the cache elements
*/
public void setExpireTime(long expireTimeMillis) {
// if expire time was <= 0 and is now greater, fill expire table now
if (expireTimeMillis > 0) {
this.expireTimeNanos = TimeUnit.NANOSECONDS.convert(expireTimeMillis, TimeUnit.MILLISECONDS);
for (Map.Entry<?, CacheLine<V>> entry: memoryTable.entrySet()) {
entry.setValue(entry.getValue().changeLine(useSoftReference, expireTimeNanos));
}
} else {
this.expireTimeNanos = 0;
// if expire time was > 0 and is now <=, do nothing, just leave the load times in place, won't hurt anything...
}
}
/** return the current expire time for the cache elements
* @return The expire time for the cache elements
*/
public long getExpireTime() {
return TimeUnit.MILLISECONDS.convert(expireTimeNanos, TimeUnit.NANOSECONDS);
}
/** Set whether or not the cache lines should use a soft reference to the data */
public void setUseSoftReference(boolean useSoftReference) {
if (this.useSoftReference != useSoftReference) {
this.useSoftReference = useSoftReference;
for (Map.Entry<?, CacheLine<V>> entry: memoryTable.entrySet()) {
entry.setValue(entry.getValue().changeLine(useSoftReference, expireTimeNanos));
}
}
}
/** Return whether or not the cache lines should use a soft reference to the data */
public boolean getUseSoftReference() {
return this.useSoftReference;
}
public boolean getUseFileSystemStore() {
return this.useFileSystemStore;
}
/** Returns the number of elements currently in the cache
* @return The number of elements currently in the cache
*/
public int size() {
if (fileTable != null) {
int size = 0;
try {
synchronized (this) {
FastIterator<Object> iter = fileTable.keys();
while (iter.next() != null) {
size++;
}
}
} catch (IOException e) {
Debug.logError(e, module);
}
return size;
} else {
return memoryTable.size();
}
}
/** Returns a boolean specifying whether or not an element with the specified key is in the cache.
* @param key The key for the element, used to reference it in the hashtables and LRU linked list
* @return True is the cache contains an element corresponding to the specified key, otherwise false
*/
public boolean containsKey(Object key) {
Object nulledKey = fromKey(key);
CacheLine<V> line = memoryTable.get(nulledKey);
if (line == null) {
if (fileTable != null) {
try {
synchronized (this) {
FastIterator<Object> iter = fileTable.keys();
Object checkKey = null;
while ((checkKey = iter.next()) != null) {
if (nulledKey.equals(checkKey)) {
return true;
}
}
}
} catch (IOException e) {
Debug.logError(e, module);
}
}
return false;
} else {
return true;
}
}
/**
* NOTE: this returns an unmodifiable copy of the keySet, so removing from here won't have an effect,
* and calling a remove while iterating through the set will not cause a concurrent modification exception.
* This behavior is necessary for now for the persisted cache feature.
*/
public Set<? extends K> getCacheLineKeys() {
// note that this must be a HashSet and not a FastSet in order to have a null value
Set<Object> keys;
if (fileTable != null) {
keys = new HashSet<Object>();
try {
synchronized (this) {
addAllFileTableKeys(keys);
}
} catch (IOException e) {
Debug.logError(e, module);
}
if (keys.remove(ObjectType.NULL)) {
keys.add(null);
}
} else {
if (memoryTable.containsKey(ObjectType.NULL)) {
keys = new HashSet<Object>(memoryTable.keySet());
keys.remove(ObjectType.NULL);
keys.add(null);
} else {
keys = memoryTable.keySet();
}
}
return Collections.unmodifiableSet(UtilGenerics.<Set<? extends K>>cast(keys));
}
public Collection<? extends CacheLine<V>> getCacheLineValues() {
throw new UnsupportedOperationException();
}
private Map<String, Object> createLineInfo(int keyNum, K key, CacheLine<V> line) {
Map<String, Object> lineInfo = new HashMap<String, Object>();
lineInfo.put("elementKey", key);
if (line.getLoadTimeNanos() > 0) {
lineInfo.put("expireTimeMillis", TimeUnit.MILLISECONDS.convert(line.getExpireTimeNanos() - System.nanoTime(), TimeUnit.NANOSECONDS));
}
lineInfo.put("lineSize", findSizeInBytes(line.getValue()));
lineInfo.put("keyNum", keyNum);
return lineInfo;
}
private Map<String, Object> createLineInfo(int keyNum, K key, V value) {
Map<String, Object> lineInfo = new HashMap<String, Object>();
lineInfo.put("elementKey", key);
lineInfo.put("lineSize", findSizeInBytes(value));
lineInfo.put("keyNum", keyNum);
return lineInfo;
}
public Collection<? extends Map<String, Object>> getLineInfos() {
List<Map<String, Object>> lineInfos = new LinkedList<Map<String, Object>>();
int keyIndex = 0;
for (K key: getCacheLineKeys()) {
Object nulledKey = fromKey(key);
if (fileTable != null) {
try {
synchronized (this) {
lineInfos.add(createLineInfo(keyIndex, key, fileTable.get(nulledKey)));
}
} catch (IOException e) {
Debug.logError(e, module);
}
} else {
CacheLine<V> line = memoryTable.get(nulledKey);
if (line != null) {
lineInfos.add(createLineInfo(keyIndex, key, line));
}
}
keyIndex++;
}
return lineInfos;
}
/** Send a key addition event to all registered listeners */
protected void noteAddition(K key, V newValue) {
for (CacheListener<K, V> listener: listeners) {
listener.noteKeyAddition(this, key, newValue);
}
}
/** Send a key removal event to all registered listeners */
protected void noteRemoval(K key, V oldValue) {
for (CacheListener<K, V> listener: listeners) {
listener.noteKeyRemoval(this, key, oldValue);
}
}
/** Send a key update event to all registered listeners */
protected void noteUpdate(K key, V newValue, V oldValue) {
for (CacheListener<K, V> listener: listeners) {
listener.noteKeyUpdate(this, key, newValue, oldValue);
}
}
/** Adds an event listener for key removals */
public void addListener(CacheListener<K, V> listener) {
listeners.add(listener);
}
/** Removes an event listener for key removals */
public void removeListener(CacheListener<K, V> listener) {
listeners.remove(listener);
}
/** Checks for a non-expired key in a specific cache */
public static boolean validKey(String cacheName, Object key) {
UtilCache<?, ?> cache = findCache(cacheName);
if (cache != null) {
if (cache.containsKey(key))
return true;
}
return false;
}
public static void clearCachesThatStartWith(String startsWith) {
for (Map.Entry<String, UtilCache<?, ?>> entry: utilCacheTable.entrySet()) {
String name = entry.getKey();
if (name.startsWith(startsWith)) {
UtilCache<?, ?> cache = entry.getValue();
cache.clear();
}
}
}
public static void clearCache(String cacheName) {
UtilCache<?, ?> cache = findCache(cacheName);
if (cache == null) return;
cache.clear();
}
@SuppressWarnings("unchecked")
public static <K, V> UtilCache<K, V> getOrCreateUtilCache(String name, int sizeLimit, int maxInMemory, long expireTime, boolean useSoftReference, boolean useFileSystemStore, String... names) {
UtilCache<K, V> existingCache = (UtilCache<K, V>) utilCacheTable.get(name);
if (existingCache != null) return existingCache;
String cacheName = name + getNextDefaultIndex(name);
UtilCache<K, V> newCache = new UtilCache<K, V>(cacheName, sizeLimit, maxInMemory, expireTime, useSoftReference, useFileSystemStore, name, names);
utilCacheTable.putIfAbsent(name, newCache);
return (UtilCache<K, V>) utilCacheTable.get(name);
}
public static <K, V> UtilCache<K, V> createUtilCache(String name, int sizeLimit, int maxInMemory, long expireTime, boolean useSoftReference, boolean useFileSystemStore, String... names) {
String cacheName = name + getNextDefaultIndex(name);
return storeCache(new UtilCache<K, V>(cacheName, sizeLimit, maxInMemory, expireTime, useSoftReference, useFileSystemStore, name, names));
}
public static <K, V> UtilCache<K, V> createUtilCache(String name, int sizeLimit, int maxInMemory, long expireTime, boolean useSoftReference, boolean useFileSystemStore) {
String cacheName = name + getNextDefaultIndex(name);
return storeCache(new UtilCache<K, V>(cacheName, sizeLimit, maxInMemory, expireTime, useSoftReference, useFileSystemStore, name));
}
public static <K,V> UtilCache<K, V> createUtilCache(String name, int sizeLimit, long expireTime, boolean useSoftReference) {
String cacheName = name + getNextDefaultIndex(name);
return storeCache(new UtilCache<K, V>(cacheName, sizeLimit, sizeLimit, expireTime, useSoftReference, false, name));
}
public static <K,V> UtilCache<K, V> createUtilCache(String name, int sizeLimit, long expireTime) {
String cacheName = name + getNextDefaultIndex(name);
return storeCache(new UtilCache<K, V>(cacheName, sizeLimit, sizeLimit, expireTime, false, false, name));
}
public static <K,V> UtilCache<K, V> createUtilCache(int sizeLimit, long expireTime) {
String cacheName = "specified" + getNextDefaultIndex("specified");
return storeCache(new UtilCache<K, V>(cacheName, sizeLimit, sizeLimit, expireTime, false, false, "specified"));
}
public static <K,V> UtilCache<K, V> createUtilCache(String name, boolean useSoftReference) {
String cacheName = name + getNextDefaultIndex(name);
return storeCache(new UtilCache<K, V>(cacheName, 0, 0, 0, useSoftReference, false, "default", name));
}
public static <K,V> UtilCache<K, V> createUtilCache(String name) {
String cacheName = name + getNextDefaultIndex(name);
return storeCache(new UtilCache<K, V>(cacheName, 0, 0, 0, false, false, "default", name));
}
public static <K,V> UtilCache<K, V> createUtilCache() {
String cacheName = "default" + getNextDefaultIndex("default");
return storeCache(new UtilCache<K, V>(cacheName, 0, 0, 0, false, false, "default"));
}
private static <K, V> UtilCache<K, V> storeCache(UtilCache<K, V> cache) {
utilCacheTable.put(cache.getName(), cache);
return cache;
}
@SuppressWarnings("unchecked")
public static <K, V> UtilCache<K, V> findCache(String cacheName) {
return (UtilCache<K, V>) UtilCache.utilCacheTable.get(cacheName);
}
@Override
public void onEviction(Object key, CacheLine<V> value) {
ExecutionPool.removePulse(value);
}
}