blob: 42a2071ac804ee03d5288a8bb87eaa09e4cba349 [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.impl;
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.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 org.ofbiz.base.util.cache.Cache;
import org.ofbiz.base.util.cache.CacheListener;
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 OFBizCache<K, V> implements Serializable, EvictionListener<Object, CacheLine<V>>, Cache<K, V> {
public static final String module = OFBizCache.class.getName();
/** 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/ofbizcache";
/** 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;
/** 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.
*/
protected OFBizCache(OFBizCacheManager cacheManager, 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);
if (propNames != null) {
setPropertiesParams(propNames);
}
long maxMemSize = this.maxInMemory;
if (maxMemSize == 0) maxMemSize = sizeLimit;
if (maxMemSize == 0) {
memoryTable = new ConcurrentHashMap<Object, CacheLine<V>>();
} else {
// FIXME: Casting a long to an int isn't great
memoryTable = new Builder<Object, CacheLine<V>>()
.maximumWeightedCapacity((int) maxMemSize)
.listener(this)
.build();
}
if (this.useFileSystemStore) {
jdbmMgr = cacheManager.getJdbmRecordManager(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);
}
}
}
}
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);
}
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#isEmpty()
*/
@Override
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();
}
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#put(K, V)
*/
@Override
public V put(K key, V value) {
return putInternal(key, value, expireTimeNanos);
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#putIfAbsent(K, V)
*/
@Override
public V putIfAbsent(K key, V value) {
return putIfAbsentInternal(key, value, expireTimeNanos);
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#putIfAbsentAndGet(K, V)
*/
@Override
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;
}
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#get(java.lang.Object)
*/
@Override
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;
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#values()
*/
@Override
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;
}
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getSizeInBytes()
*/
@Override
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;
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#remove(java.lang.Object)
*/
@Override
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());
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#erase()
*/
@Override
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();
}
}
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#clear()
*/
@Override
public void clear() {
erase();
clearCounters();
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getName()
*/
@Override
public String getName() {
return this.name;
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getHitCount()
*/
@Override
public long getHitCount() {
return this.hitCount.get();
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getMissCountNotFound()
*/
@Override
public long getMissCountNotFound() {
return this.missCountNotFound.get();
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getMissCountExpired()
*/
@Override
public long getMissCountExpired() {
return this.missCountExpired.get();
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getMissCountSoftRef()
*/
@Override
public long getMissCountSoftRef() {
return this.missCountSoftRef.get();
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getMissCountTotal()
*/
@Override
public long getMissCountTotal() {
return getMissCountSoftRef() + getMissCountNotFound() + getMissCountExpired();
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getRemoveHitCount()
*/
@Override
public long getRemoveHitCount() {
return this.removeHitCount.get();
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getRemoveMissCount()
*/
@Override
public long getRemoveMissCount() {
return this.removeMissCount.get();
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#clearCounters()
*/
@Override
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);
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#setMaxInMemory(int)
*/
@Override
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);
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getMaxInMemory()
*/
@Override
public int getMaxInMemory() {
return maxInMemory;
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#setSizeLimit(int)
*/
@Override
public void setSizeLimit(int newSizeLimit) {
this.sizeLimit = newSizeLimit;
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getSizeLimit()
*/
@Override
public int getSizeLimit() {
return sizeLimit;
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#setExpireTime(long)
*/
@Override
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...
}
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getExpireTime()
*/
@Override
public long getExpireTime() {
return TimeUnit.MILLISECONDS.convert(expireTimeNanos, TimeUnit.NANOSECONDS);
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#setUseSoftReference(boolean)
*/
@Override
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));
}
}
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getUseSoftReference()
*/
@Override
public boolean getUseSoftReference() {
return this.useSoftReference;
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getUseFileSystemStore()
*/
@Override
public boolean getUseFileSystemStore() {
return this.useFileSystemStore;
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#size()
*/
@Override
public long 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();
}
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#containsKey(java.lang.Object)
*/
@Override
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;
}
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getCacheLineKeys()
*/
@Override
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;
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#getLineInfos()
*/
@Override
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);
}
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#addListener(org.ofbiz.base.util.cache.CacheListener)
*/
@Override
public void addListener(CacheListener<K, V> listener) {
listeners.add(listener);
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#removeListener(org.ofbiz.base.util.cache.CacheListener)
*/
@Override
public void removeListener(CacheListener<K, V> listener) {
listeners.remove(listener);
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.cache.Cache#onEviction(java.lang.Object, org.ofbiz.base.util.cache.CacheLine)
*/
@Override
public void onEviction(Object key, CacheLine<V> value) {
ExecutionPool.removePulse(value);
}
}