The CacheAdapter interface has changed:
* No more CacheBins, operations happen directly on the adapter. * Each cache write must supply a CacheCategory value that identifies its type. Current values are RECORD, KEYSET and RESULT. We will probably need a MISC or USER or something along those lines, too. The EHCache adapter has been entirely rewritten: * Each CacheCategory maps to one EHCache, which means there are only a very few EHCaches, and we know what they are. We expect these to be declared in ehcache.xml rather than programmatically generated as before. * Elements inside each EHCache are indexed by (cacheId, elementKey) tuples. The main purpose of this refactor is to sort all Records into a single EHCache, which we can then make really, really large, and then let the LRU mechanism sort out what should be in memory and what shouldn't. The previous implementation would make no ram allocation distinction between busy records and ones rarely read.
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
//
|
//
|
||||||
// Depot library - a Java relational persistence library
|
// Depot library - a Java relational persistence library
|
||||||
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
|
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
|
||||||
//
|
//
|
||||||
// This library is free software; you can redistribute it and/or modify it
|
// This library is free software; you can redistribute it and/or modify it
|
||||||
// under the terms of the GNU Lesser General Public License as published
|
// under the terms of the GNU Lesser General Public License as published
|
||||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||||
@@ -22,14 +22,31 @@ package com.samskivert.depot;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import com.samskivert.depot.impl.FindAllQuery;
|
||||||
|
import com.samskivert.util.Tuple;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementations of this interface are responsible for all the caching needs of Depot.
|
* Implementations of this interface are responsible for all the caching needs of Depot.
|
||||||
*
|
*
|
||||||
* The cache consists of many {@link CacheBin}s. Each bin its own key space. Look-ups and storage
|
* From the point of view of this interface, there are a potentially very large number of
|
||||||
* occur on a per-bin per-key basis.
|
* caches available, each idenfided by a unique cacheId. Currently Depot creates up to three
|
||||||
|
* caches for each record type:
|
||||||
|
*
|
||||||
|
* Any record type with a primary key has a {@link CacheCategory#RECORD} cache, for storing
|
||||||
|
* record instances by primary key.
|
||||||
|
*
|
||||||
|
* Record types with primary keys may also have a {@link CacheCategory#KEYSET} cache wherein
|
||||||
|
* {@link KeySet} instances are stored, identified by query strings. See {@link FindAllQuery}
|
||||||
|
* for more on this.
|
||||||
|
*
|
||||||
|
* Finally, clients may request {@link CacheCategory#RESULT} caching of entire result sets of
|
||||||
|
* some record type -- which does not need to have a primary key, in contrast to the other two
|
||||||
|
* categories. These are also identified by query strings, and end up in a third cache.
|
||||||
*/
|
*/
|
||||||
public interface CacheAdapter
|
public interface CacheAdapter
|
||||||
{
|
{
|
||||||
|
public enum CacheCategory { RECORD, KEYSET, RESULT };
|
||||||
|
|
||||||
/** The encapsulated result of a cache lookup. */
|
/** The encapsulated result of a cache lookup. */
|
||||||
public interface CachedValue<T>
|
public interface CachedValue<T>
|
||||||
{
|
{
|
||||||
@@ -38,37 +55,25 @@ public interface CacheAdapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reference to a specific bin within the cache; this is the type where most of the actual
|
* Searches the given cache using the given key and returns the resulting {@link CachedValue},
|
||||||
* caching functionality occurs.
|
* or null if nothing exists in the cache for this key.
|
||||||
*/
|
*/
|
||||||
public interface CacheBin<T>
|
public <T> CachedValue<T> lookup (String cacheId, Serializable key);
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Searches this bin using the given key and returns the resulting {@link CachedValue}, or
|
|
||||||
* null if nothing exists in the cache for this key.
|
|
||||||
*/
|
|
||||||
public CachedValue<T> lookup (Serializable key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores a new value in this cache bin under the given key.
|
|
||||||
*/
|
|
||||||
public void store (Serializable key, T value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the cache entry, if any, associated with the given key.
|
|
||||||
*/
|
|
||||||
public void remove (Serializable key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a way to enumerate the currently cached entries in this bin.
|
|
||||||
*/
|
|
||||||
public Iterable<Serializable> enumerateKeys ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the {@link CacheBin} associated with the given ID, creating one on the fly if needed.
|
* Stores a new value in the given cache under the given key.
|
||||||
*/
|
*/
|
||||||
public <T> CacheBin<T> getCache (String id);
|
public <T> void store (CacheCategory category, String cacheId, Serializable key, T value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the cache entry, if any, associated with the given key.
|
||||||
|
*/
|
||||||
|
public void remove (String cacheId, Serializable key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a way to enumerate the currently cached entries for the given cache.
|
||||||
|
*/
|
||||||
|
public <T> Iterable<Tuple<Serializable, CachedValue<T>>> enumerate (String cacheId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shut down all operations, e.g. persisting memory contents to disk.
|
* Shut down all operations, e.g. persisting memory contents to disk.
|
||||||
|
|||||||
@@ -21,124 +21,108 @@
|
|||||||
package com.samskivert.depot;
|
package com.samskivert.depot;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.samskivert.util.Tuple;
|
||||||
|
|
||||||
import net.sf.ehcache.Cache;
|
|
||||||
import net.sf.ehcache.CacheManager;
|
import net.sf.ehcache.CacheManager;
|
||||||
|
import net.sf.ehcache.Ehcache;
|
||||||
import net.sf.ehcache.Element;
|
import net.sf.ehcache.Element;
|
||||||
import net.sf.ehcache.distribution.CacheManagerPeerListener;
|
import net.sf.ehcache.event.CacheEventListener;
|
||||||
import net.sf.ehcache.distribution.CacheManagerPeerProvider;
|
|
||||||
import net.sf.ehcache.distribution.RMIAsynchronousCacheReplicator;
|
|
||||||
|
|
||||||
import static com.samskivert.depot.Log.log;
|
import static com.samskivert.depot.Log.log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of {@link CacheAdapter} for ehcache.
|
* An implementation of {@link CacheAdapter} for ehcache where each {@link CacheCategory} results
|
||||||
|
* in one {@link Ehcache}. All (cacheId, key) combinations within one category is stuffed into the
|
||||||
|
* same {@link Ehcache}, and all elements are cached under {@link EHCacheKey}, which basically
|
||||||
|
* wraps just such a tuple.
|
||||||
|
*
|
||||||
|
* Thus there are currently only three Ehcaches in play, called 'depotRecord', 'depotKeyset', and
|
||||||
|
* 'depotResult'. These must be defined in your ehcache.xml configuration. If you use distributed
|
||||||
|
* replication/invalidation, you should replicate updates and removes but not puts nor
|
||||||
|
* updates-via-copy.
|
||||||
*/
|
*/
|
||||||
public class EHCacheAdapter
|
public class EHCacheAdapter
|
||||||
implements CacheAdapter
|
implements CacheAdapter
|
||||||
{
|
{
|
||||||
|
protected static final String EHCACHE_RECORD_CACHE = "depotRecord";
|
||||||
|
protected static final String EHCACHE_KEYSET_CACHE = "depotKeyset";
|
||||||
|
protected static final String EHCACHE_RESULT_CACHE = "depotResult";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an adapter using the supplied cache manager. Note: this adapter does not shut down
|
* Creates an adapter using the supplied cache manager. Note: this adapter does not shut down
|
||||||
* the supplied manager when it is shutdown. The caller is responsible for shutting down the
|
* the supplied manager when it is shutdown. The caller is responsible for shutting down the
|
||||||
* cache manager when it knows that Depot and any other clients no longer need it.
|
* cache manager when it knows that Depot and any other clients no longer need it.
|
||||||
*/
|
*/
|
||||||
public EHCacheAdapter (EHCacheConfig config, CacheManager cachemgr)
|
public EHCacheAdapter (CacheManager cachemgr)
|
||||||
{
|
{
|
||||||
_config = config;
|
bindEHCache(cachemgr, CacheCategory.RECORD, EHCACHE_RECORD_CACHE);
|
||||||
_cachemgr = cachemgr;
|
bindEHCache(cachemgr, CacheCategory.KEYSET, EHCACHE_KEYSET_CACHE);
|
||||||
|
bindEHCache(cachemgr, CacheCategory.RESULT, EHCACHE_RESULT_CACHE);
|
||||||
|
}
|
||||||
|
|
||||||
if (config.distributed) {
|
// from CacheAdapter
|
||||||
CacheManagerPeerListener listener = _cachemgr.getCachePeerListener();
|
public <T> CachedValue<T> lookup (String cacheId, Serializable key)
|
||||||
CacheManagerPeerProvider provider = _cachemgr.getCachePeerProvider();
|
{
|
||||||
if (provider == null || listener == null) {
|
@SuppressWarnings("unchecked")
|
||||||
log.warning("Distributed mode disabled. Please set a CacheManagerPeerListener " +
|
EHCacheBin<T> bin = (EHCacheBin<T>) _bins.get(cacheId);
|
||||||
" and a CacheManagerPeerProvider in your ehcache.xml configuration",
|
if (bin == null) {
|
||||||
"listener", listener, "provider", provider);
|
return null;
|
||||||
_config.distributed = false;
|
}
|
||||||
}
|
return lookup(bin.getCache(), cacheId, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// from CacheAdapter
|
||||||
|
public <T> void store (CacheCategory category, String cacheId, Serializable key, T value)
|
||||||
|
{
|
||||||
|
Ehcache cache = _categories.get(category);
|
||||||
|
if (cache == null) {
|
||||||
|
throw new IllegalArgumentException("Unknown category: " + category);
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
EHCacheBin<T> bin = (EHCacheBin<T>) _bins.get(cacheId);
|
||||||
|
if (bin == null) {
|
||||||
|
bin = new EHCacheBin<T>(cache, cacheId);
|
||||||
|
_bins.put(cacheId, bin);
|
||||||
|
}
|
||||||
|
bin.getCache().put(new Element(new EHCacheKey(cacheId, key), value != null ? value : NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
// from CacheAdapter
|
||||||
|
public void remove (String cacheId, Serializable key)
|
||||||
|
{
|
||||||
|
EHCacheBin<?> bin = _bins.get(cacheId);
|
||||||
|
if (bin != null) {
|
||||||
|
bin.getCache().remove(new EHCacheKey(cacheId, key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> CacheBin<T> getCache (String id)
|
// from CacheAdapter
|
||||||
|
public <T> Iterable<Tuple<Serializable, CachedValue<T>>> enumerate (final String cacheId)
|
||||||
{
|
{
|
||||||
return new EHCacheBin<T>(id);
|
EHCacheBin<?> bin = _bins.get(cacheId);
|
||||||
}
|
if (bin == null) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
final Ehcache cache = bin.getCache();
|
||||||
* The main ehcache-bridging class, a {@link CacheBin} interface against {@link Cache}.
|
Iterable<Tuple<Serializable, CachedValue<T>>> tuples = Iterables.transform(
|
||||||
*/
|
bin.getKeys(), new Function<Serializable, Tuple<Serializable, CachedValue<T>>> () {
|
||||||
protected class EHCacheBin<T> implements CacheBin<T>
|
public Tuple<Serializable, CachedValue<T>> apply (Serializable key) {
|
||||||
{
|
CachedValue<T> value = lookup(cache, cacheId, key);
|
||||||
// from CacheBin
|
return (value != null) ? Tuple.newTuple(key, value) : null;
|
||||||
public CachedValue<T> lookup (Serializable key)
|
|
||||||
{
|
|
||||||
Element hit = _cache.get(key);
|
|
||||||
if (hit == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serializable rawValue = hit.getValue();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final T value = (T) (rawValue instanceof NullValue ? null : rawValue);
|
|
||||||
return new CachedValue<T>() {
|
|
||||||
public T getValue () {
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
@Override public String toString () {
|
});
|
||||||
return String.valueOf(value);
|
|
||||||
}
|
return Iterables.filter(tuples, Predicates.notNull());
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// from CacheBin
|
|
||||||
public void store (Serializable key, T value)
|
|
||||||
{
|
|
||||||
_cache.put(new Element(key, value != null ? value : NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
// from CacheBin
|
|
||||||
public void remove (Serializable key)
|
|
||||||
{
|
|
||||||
_cache.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// from CacheBin
|
|
||||||
public Iterable<Serializable> enumerateKeys ()
|
|
||||||
{
|
|
||||||
@SuppressWarnings("unchecked") Iterable<Serializable> keys = _cache.getKeys();
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected EHCacheBin (String id)
|
|
||||||
{
|
|
||||||
synchronized (_cachemgr) {
|
|
||||||
_cache = _cachemgr.getCache(id);
|
|
||||||
if (_cache == null) {
|
|
||||||
// create the cache programatically with reasonable settings
|
|
||||||
_cache = new Cache(id,
|
|
||||||
_config.elementsInMemory,
|
|
||||||
_config.overflowToDisk,
|
|
||||||
false,
|
|
||||||
_config.timeToLiveSeconds,
|
|
||||||
_config.timeToIdleSeconds);
|
|
||||||
|
|
||||||
if (_config.distributed) {
|
|
||||||
// a programatically created cache has to have its replicator event
|
|
||||||
// listener programatically added.
|
|
||||||
_cache.getCacheEventNotificationService().registerListener(
|
|
||||||
new RMIAsynchronousCacheReplicator(
|
|
||||||
_config.sendNewRecordsToPeers,
|
|
||||||
_config.invalidateUpdatedRecordsOnPeers,
|
|
||||||
_config.sendUpdatedRecordsToPeers,
|
|
||||||
_config.invalidateRemovedRecordsOnPeers,
|
|
||||||
_config.replicationInterval));
|
|
||||||
}
|
|
||||||
_cachemgr.addCache(_cache);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Cache _cache;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// from CacheAdapter
|
// from CacheAdapter
|
||||||
@@ -146,12 +130,186 @@ public class EHCacheAdapter
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected EHCacheConfig _config;
|
protected <T> CachedValue<T> lookup (Ehcache cache, String cacheId, Serializable key)
|
||||||
protected CacheManager _cachemgr;
|
{
|
||||||
|
Element hit = cache.get(new EHCacheKey(cacheId, key));
|
||||||
|
if (hit == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serializable rawValue = hit.getValue();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final T value = (T) (rawValue instanceof NullValue ? null : rawValue);
|
||||||
|
return new CachedValue<T>() {
|
||||||
|
public T getValue () {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
@Override public String toString () {
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Ehcache bindEHCache (CacheManager cachemgr, CacheCategory category, String cacheName)
|
||||||
|
{
|
||||||
|
Ehcache cache = cachemgr.getCache(cacheName);
|
||||||
|
if (cache == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Could not find Ehcache '" + cacheName + "'. Please fix your ehcache configuration.");
|
||||||
|
}
|
||||||
|
cache.getCacheEventNotificationService().registerListener(_cacheEventListener);
|
||||||
|
_categories.put(category, cache);
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<CacheCategory, Ehcache> _categories = Maps.newConcurrentHashMap();
|
||||||
|
protected Map<String, EHCacheBin<?>> _bins = Maps.newConcurrentHashMap();
|
||||||
|
|
||||||
|
protected CacheEventListener _cacheEventListener = new CacheEventListener() {
|
||||||
|
public Object clone () throws CloneNotSupportedException {
|
||||||
|
throw new CloneNotSupportedException();
|
||||||
|
}
|
||||||
|
public void dispose () {}
|
||||||
|
public void notifyElementEvicted (Ehcache cache, Element element) {
|
||||||
|
log.debug("notifyElementEvicted(" + cache + ", " + element + ")");
|
||||||
|
removeFromBin(cache, element);
|
||||||
|
}
|
||||||
|
public void notifyElementExpired (Ehcache cache, Element element) {
|
||||||
|
log.debug("notifyElementExpired(" + cache + ", " + element + ")");
|
||||||
|
removeFromBin(cache, element);
|
||||||
|
}
|
||||||
|
public void notifyElementPut (Ehcache cache, Element element) {
|
||||||
|
log.debug("notifyElementPut(" + cache + ", " + element + ")");
|
||||||
|
addToBin(cache, element);
|
||||||
|
}
|
||||||
|
public void notifyElementRemoved (Ehcache cache, Element element) {
|
||||||
|
log.debug("notifyElementRemoved(" + cache + ", " + element + ")");
|
||||||
|
removeFromBin(cache, element);
|
||||||
|
}
|
||||||
|
public void notifyElementUpdated (Ehcache cache, Element element) {
|
||||||
|
log.debug("notifyElementUpdated(" + cache + ", " + element + ")");
|
||||||
|
addToBin(cache, element);
|
||||||
|
}
|
||||||
|
public void notifyRemoveAll (Ehcache cache) {}
|
||||||
|
|
||||||
|
protected void removeFromBin (Ehcache cache, Element element)
|
||||||
|
{
|
||||||
|
EHCacheKey key = (EHCacheKey)element.getKey();
|
||||||
|
EHCacheBin<?> bin = _bins.get(key.getCacheId());
|
||||||
|
if (bin == null) {
|
||||||
|
log.warning("Dropping element removal without cache bin", "key", key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bin.removeKey(key.getCacheKey());
|
||||||
|
for (Tuple<Serializable, CachedValue<Object>> tuple :
|
||||||
|
enumerate(((EHCacheKey) element.getKey()).getCacheId())) {
|
||||||
|
log.debug("Enumeration: " + tuple);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void addToBin (Ehcache cache, Element element)
|
||||||
|
{
|
||||||
|
EHCacheKey key = (EHCacheKey)element.getKey();
|
||||||
|
EHCacheBin<?> bin = _bins.get(key.getCacheId());
|
||||||
|
if (bin == null) {
|
||||||
|
log.warning("Dropping element addition without cache bin", "key", key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bin.addKey(key.getCacheKey());
|
||||||
|
for (Tuple<Serializable, CachedValue<Object>> tuple :
|
||||||
|
enumerate(((EHCacheKey) element.getKey()).getCacheId())) {
|
||||||
|
log.debug("Enumeration: " + tuple);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected static class EHCacheBin<T>
|
||||||
|
{
|
||||||
|
public EHCacheBin (Ehcache cache, String id)
|
||||||
|
{
|
||||||
|
_cache = cache;
|
||||||
|
_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Serializable> getKeys ()
|
||||||
|
{
|
||||||
|
return _keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addKey (Serializable key)
|
||||||
|
{
|
||||||
|
_keys.add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeKey (Serializable key)
|
||||||
|
{
|
||||||
|
_keys.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Ehcache getCache ()
|
||||||
|
{
|
||||||
|
return _cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Ehcache _cache;
|
||||||
|
protected String _id;
|
||||||
|
|
||||||
|
protected Set<Serializable> _keys = Sets.newConcurrentHashSet();
|
||||||
|
}
|
||||||
|
|
||||||
// this is just for convenience and memory use; we don't rely on pointer equality anywhere
|
// this is just for convenience and memory use; we don't rely on pointer equality anywhere
|
||||||
protected static Serializable NULL = new NullValue() {};
|
protected static Serializable NULL = new NullValue() {};
|
||||||
|
|
||||||
|
/** A class to wrap a Depot id/key into an EHCache key. */
|
||||||
|
protected static class EHCacheKey
|
||||||
|
implements Serializable
|
||||||
|
{
|
||||||
|
public EHCacheKey (String id, Serializable key)
|
||||||
|
{
|
||||||
|
if (id == null || key == null) {
|
||||||
|
throw new IllegalArgumentException("Can't handle null key or id");
|
||||||
|
}
|
||||||
|
_id = id;
|
||||||
|
_key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCacheId () {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Serializable getCacheKey ()
|
||||||
|
{
|
||||||
|
return _key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return "[" + _id + ", " + _key + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode ()
|
||||||
|
{
|
||||||
|
return 31 * _id.hashCode() + _key.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals (Object obj)
|
||||||
|
{
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _id.equals(((EHCacheKey) obj)._id) && _key.equals(((EHCacheKey) obj)._key);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String _id;
|
||||||
|
protected Serializable _key;
|
||||||
|
}
|
||||||
|
|
||||||
/** A class to represent an explicitly Serializable concept of null for EHCache. */
|
/** A class to represent an explicitly Serializable concept of null for EHCache. */
|
||||||
protected static class NullValue implements Serializable
|
protected static class NullValue implements Serializable
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
//
|
|
||||||
// $Id: EHCacheAdapter.java 325 2008-11-16 08:03:33Z samskivert $
|
|
||||||
//
|
|
||||||
// Depot library - a Java relational persistence library
|
|
||||||
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
|
|
||||||
//
|
|
||||||
// This library is free software; you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU Lesser General Public License as published
|
|
||||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
// Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public
|
|
||||||
// License along with this library; if not, write to the Free Software
|
|
||||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
|
|
||||||
package com.samskivert.depot;
|
|
||||||
|
|
||||||
public class EHCacheConfig
|
|
||||||
{
|
|
||||||
/** The maximum number of cached records to keep in memory. */
|
|
||||||
public int elementsInMemory;
|
|
||||||
|
|
||||||
/** Whether or not to overflow records to disk. */
|
|
||||||
public boolean overflowToDisk;
|
|
||||||
|
|
||||||
/** How long to keep a record in the cache after it's been stored or updated. */
|
|
||||||
public int timeToLiveSeconds;
|
|
||||||
|
|
||||||
/** How long to keep a record in the cache after it's last accessed. */
|
|
||||||
public int timeToIdleSeconds;
|
|
||||||
|
|
||||||
/** Whether or not this is a distributed cache. Setting this to true makes various demands
|
|
||||||
* on your ehcache.xml configuration. */
|
|
||||||
public boolean distributed;
|
|
||||||
|
|
||||||
/** If true, newly cached records will be serialized and sent to all peer caches. */
|
|
||||||
public boolean sendNewRecordsToPeers;
|
|
||||||
|
|
||||||
/** If true, modification of a cached record will remove any cached versions on peers. */
|
|
||||||
public boolean invalidateUpdatedRecordsOnPeers;
|
|
||||||
|
|
||||||
/** If true, modification of a cached record will send the new version to all peer caches. */
|
|
||||||
public boolean sendUpdatedRecordsToPeers;
|
|
||||||
|
|
||||||
/** If true, removal of a cached record will remove any cached versions on peers. */
|
|
||||||
public boolean invalidateRemovedRecordsOnPeers;
|
|
||||||
|
|
||||||
/** How often to perform replication, in milliseconds. */
|
|
||||||
public int replicationInterval;
|
|
||||||
|
|
||||||
public EHCacheConfig (int elementsInMemory, boolean overflowToDisk, int timeToLiveSeconds,
|
|
||||||
int timeToIdleSeconds, boolean distributed, boolean sendNewRecordsToPeers,
|
|
||||||
boolean sendUpdatedRecordsToPeers, boolean invalidateUpdatedRecordsOnPeers,
|
|
||||||
boolean invalidateRemovedRecordsOnPeers, int replicationInterval)
|
|
||||||
{
|
|
||||||
this.elementsInMemory = elementsInMemory;
|
|
||||||
this.overflowToDisk = overflowToDisk;
|
|
||||||
this.timeToLiveSeconds = timeToLiveSeconds;
|
|
||||||
this.timeToIdleSeconds = timeToIdleSeconds;
|
|
||||||
this.distributed = distributed;
|
|
||||||
this.sendNewRecordsToPeers = sendNewRecordsToPeers;
|
|
||||||
this.sendUpdatedRecordsToPeers = sendUpdatedRecordsToPeers;
|
|
||||||
this.invalidateUpdatedRecordsOnPeers = invalidateUpdatedRecordsOnPeers;
|
|
||||||
this.invalidateRemovedRecordsOnPeers = invalidateRemovedRecordsOnPeers;
|
|
||||||
this.replicationInterval = replicationInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EHCacheConfig (int elementsInMemory, boolean overflowToDisk, int timeToLiveSeconds,
|
|
||||||
int timeToIdleSeconds)
|
|
||||||
{
|
|
||||||
this(elementsInMemory, overflowToDisk, timeToLiveSeconds, timeToIdleSeconds, false,
|
|
||||||
false, false, false, false, -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
//
|
//
|
||||||
// Depot library - a Java relational persistence library
|
// Depot library - a Java relational persistence library
|
||||||
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
|
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
|
||||||
//
|
//
|
||||||
// This library is free software; you can redistribute it and/or modify it
|
// This library is free software; you can redistribute it and/or modify it
|
||||||
// under the terms of the GNU Lesser General Public License as published
|
// under the terms of the GNU Lesser General Public License as published
|
||||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||||
@@ -34,6 +34,7 @@ import com.google.common.collect.Sets;
|
|||||||
|
|
||||||
import com.samskivert.io.PersistenceException;
|
import com.samskivert.io.PersistenceException;
|
||||||
import com.samskivert.util.StringUtil;
|
import com.samskivert.util.StringUtil;
|
||||||
|
import com.samskivert.util.Tuple;
|
||||||
|
|
||||||
import com.samskivert.jdbc.ConnectionProvider;
|
import com.samskivert.jdbc.ConnectionProvider;
|
||||||
import com.samskivert.jdbc.DatabaseLiaison;
|
import com.samskivert.jdbc.DatabaseLiaison;
|
||||||
@@ -42,6 +43,8 @@ import com.samskivert.jdbc.LiaisonRegistry;
|
|||||||
import com.samskivert.jdbc.MySQLLiaison;
|
import com.samskivert.jdbc.MySQLLiaison;
|
||||||
import com.samskivert.jdbc.PostgreSQLLiaison;
|
import com.samskivert.jdbc.PostgreSQLLiaison;
|
||||||
|
|
||||||
|
import com.samskivert.depot.CacheAdapter.CacheCategory;
|
||||||
|
import com.samskivert.depot.CacheAdapter.CachedValue;
|
||||||
import com.samskivert.depot.annotation.TableGenerator;
|
import com.samskivert.depot.annotation.TableGenerator;
|
||||||
import com.samskivert.depot.impl.DepotMarshaller;
|
import com.samskivert.depot.impl.DepotMarshaller;
|
||||||
import com.samskivert.depot.impl.DepotTypes;
|
import com.samskivert.depot.impl.DepotTypes;
|
||||||
@@ -317,15 +320,14 @@ public class PersistenceContext
|
|||||||
if (_cache == null) {
|
if (_cache == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
CacheAdapter.CacheBin<T> bin = _cache.getCache(key.getCacheId());
|
CacheAdapter.CachedValue<T> ref = _cache.lookup(key.getCacheId(), key.getCacheKey());
|
||||||
CacheAdapter.CachedValue<T> ref = bin.lookup(key.getCacheKey());
|
|
||||||
return (ref == null) ? null : ref.getValue();
|
return (ref == null) ? null : ref.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a new entry indexed by the given key.
|
* Stores a new entry indexed by the given key.
|
||||||
*/
|
*/
|
||||||
public <T> void cacheStore (CacheKey key, T entry)
|
public <T> void cacheStore (CacheCategory category, CacheKey key, T entry)
|
||||||
{
|
{
|
||||||
if (_cache == null) {
|
if (_cache == null) {
|
||||||
return;
|
return;
|
||||||
@@ -337,12 +339,11 @@ public class PersistenceContext
|
|||||||
}
|
}
|
||||||
log.debug("storing [key=" + key + ", value=" + entry + "]");
|
log.debug("storing [key=" + key + ", value=" + entry + "]");
|
||||||
|
|
||||||
CacheAdapter.CacheBin<T> bin = _cache.getCache(key.getCacheId());
|
CacheAdapter.CachedValue<T> element = _cache.lookup(key.getCacheId(), key.getCacheKey());
|
||||||
CacheAdapter.CachedValue<T> element = bin.lookup(key.getCacheKey());
|
|
||||||
T oldEntry = (element != null ? element.getValue() : null);
|
T oldEntry = (element != null ? element.getValue() : null);
|
||||||
|
|
||||||
// update the cache
|
// update the cache
|
||||||
bin.store(key.getCacheKey(), entry);
|
_cache.store(category, key.getCacheId(), key.getCacheKey(), entry);
|
||||||
|
|
||||||
// then do cache invalidations
|
// then do cache invalidations
|
||||||
Set<CacheListener<?>> listeners = _listenerSets.get(key.getCacheId());
|
Set<CacheListener<?>> listeners = _listenerSets.get(key.getCacheId());
|
||||||
@@ -392,8 +393,7 @@ public class PersistenceContext
|
|||||||
log.info("Invalidating", "id", cacheId, "key", cacheKey);
|
log.info("Invalidating", "id", cacheId, "key", cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
CacheAdapter.CacheBin<T> bin = _cache.getCache(cacheId);
|
CacheAdapter.CachedValue<T> element = _cache.lookup(cacheId, cacheKey);
|
||||||
CacheAdapter.CachedValue<T> element = bin.lookup(cacheKey);
|
|
||||||
if (element != null) {
|
if (element != null) {
|
||||||
// find the old entry, if any
|
// find the old entry, if any
|
||||||
T oldEntry = element.getValue();
|
T oldEntry = element.getValue();
|
||||||
@@ -413,7 +413,7 @@ public class PersistenceContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
// then remove the keyed entry from the cache system
|
// then remove the keyed entry from the cache system
|
||||||
bin.remove(cacheKey);
|
_cache.remove(cacheId, cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -435,15 +435,10 @@ public class PersistenceContext
|
|||||||
if (_cache == null) {
|
if (_cache == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CacheAdapter.CacheBin<T> bin = _cache.getCache(cacheId);
|
|
||||||
if (bin != null) {
|
Iterable<Tuple<Serializable, CachedValue<T>>> entries = _cache.<T>enumerate(cacheId);
|
||||||
for (Object key : bin.enumerateKeys()) {
|
for (Tuple<Serializable, CachedValue<T>> entry : entries) {
|
||||||
CacheAdapter.CachedValue<T> element = bin.lookup((Serializable) key);
|
filter.visitCacheEntry(this, cacheId, entry.left, entry.right.getValue());
|
||||||
T value;
|
|
||||||
if (element != null && (value = element.getValue()) != null) {
|
|
||||||
filter.visitCacheEntry(this, cacheId, (Serializable) key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import com.samskivert.depot.PersistenceContext;
|
|||||||
import com.samskivert.depot.PersistentRecord;
|
import com.samskivert.depot.PersistentRecord;
|
||||||
import com.samskivert.depot.SimpleCacheKey;
|
import com.samskivert.depot.SimpleCacheKey;
|
||||||
import com.samskivert.depot.Stats;
|
import com.samskivert.depot.Stats;
|
||||||
|
import com.samskivert.depot.CacheAdapter.CacheCategory;
|
||||||
import com.samskivert.depot.clause.FieldOverride;
|
import com.samskivert.depot.clause.FieldOverride;
|
||||||
import com.samskivert.depot.clause.QueryClause;
|
import com.samskivert.depot.clause.QueryClause;
|
||||||
import com.samskivert.depot.clause.SelectClause;
|
import com.samskivert.depot.clause.SelectClause;
|
||||||
@@ -134,7 +135,8 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
"keys", keysToString(_keys), "cached", (_qkey != null));
|
"keys", keysToString(_keys), "cached", (_qkey != null));
|
||||||
}
|
}
|
||||||
if (_qkey != null) {
|
if (_qkey != null) {
|
||||||
ctx.cacheStore(_qkey, _keys); // cache the resulting key set
|
// cache the resulting key set
|
||||||
|
ctx.cacheStore(CacheCategory.KEYSET, _qkey, _keys);
|
||||||
}
|
}
|
||||||
// and fetch any records we can from the cache
|
// and fetch any records we can from the cache
|
||||||
_fetchKeys = loadFromCache(ctx, _keys, _entities);
|
_fetchKeys = loadFromCache(ctx, _keys, _entities);
|
||||||
@@ -239,7 +241,7 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
result.size(), "cacheKey", _qkey);
|
result.size(), "cacheKey", _qkey);
|
||||||
}
|
}
|
||||||
if (_qkey != null) {
|
if (_qkey != null) {
|
||||||
ctx.cacheStore(_qkey, result); // cache the entire result set
|
ctx.cacheStore(CacheCategory.RESULT, _qkey, result); // cache the entire result set
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -344,7 +346,7 @@ public abstract class FindAllQuery<T extends PersistentRecord> extends Query<Lis
|
|||||||
if (entities.put(key, obj) != null) {
|
if (entities.put(key, obj) != null) {
|
||||||
dups++;
|
dups++;
|
||||||
}
|
}
|
||||||
ctx.cacheStore(key, obj.clone()); // cache our result
|
ctx.cacheStore(CacheCategory.RECORD, key, obj.clone()); // cache our result
|
||||||
got.add(key);
|
got.add(key);
|
||||||
cnt++;
|
cnt++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import com.samskivert.depot.DepotRepository;
|
|||||||
import com.samskivert.depot.PersistenceContext;
|
import com.samskivert.depot.PersistenceContext;
|
||||||
import com.samskivert.depot.PersistentRecord;
|
import com.samskivert.depot.PersistentRecord;
|
||||||
import com.samskivert.depot.Stats;
|
import com.samskivert.depot.Stats;
|
||||||
|
import com.samskivert.depot.CacheAdapter.CacheCategory;
|
||||||
import com.samskivert.depot.clause.QueryClause;
|
import com.samskivert.depot.clause.QueryClause;
|
||||||
import com.samskivert.depot.clause.SelectClause;
|
import com.samskivert.depot.clause.SelectClause;
|
||||||
import com.samskivert.depot.clause.WhereClause;
|
import com.samskivert.depot.clause.WhereClause;
|
||||||
@@ -104,7 +105,7 @@ public class FindOneQuery<T extends PersistentRecord> extends Query<T>
|
|||||||
log.info("Loaded " + (key != null ? key : _marsh.getTableName()));
|
log.info("Loaded " + (key != null ? key : _marsh.getTableName()));
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
ctx.cacheStore(key, (result != null) ? result.clone() : null);
|
ctx.cacheStore(CacheCategory.RECORD, key, (result != null) ? result.clone() : null);
|
||||||
if (PersistenceContext.CACHE_DEBUG) {
|
if (PersistenceContext.CACHE_DEBUG) {
|
||||||
log.info("Cached " + key);
|
log.info("Cached " + key);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import com.samskivert.depot.CacheKey;
|
|||||||
import com.samskivert.depot.PersistenceContext;
|
import com.samskivert.depot.PersistenceContext;
|
||||||
import com.samskivert.depot.PersistentRecord;
|
import com.samskivert.depot.PersistentRecord;
|
||||||
import com.samskivert.depot.Stats;
|
import com.samskivert.depot.Stats;
|
||||||
|
import com.samskivert.depot.CacheAdapter.CacheCategory;
|
||||||
import com.samskivert.jdbc.DatabaseLiaison;
|
import com.samskivert.jdbc.DatabaseLiaison;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,7 +98,7 @@ public abstract class Modifier implements Operation<Integer>
|
|||||||
Integer rows = super.invoke(ctx, conn, liaison);
|
Integer rows = super.invoke(ctx, conn, liaison);
|
||||||
// if we have both a key and a record, cache
|
// if we have both a key and a record, cache
|
||||||
if (_key != null && _result != null) {
|
if (_key != null && _result != null) {
|
||||||
ctx.cacheStore(_key, _result.clone());
|
ctx.cacheStore(CacheCategory.RECORD, _key, _result.clone());
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//
|
//
|
||||||
// Depot library - a Java relational persistence library
|
// Depot library - a Java relational persistence library
|
||||||
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
|
// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell
|
||||||
//
|
//
|
||||||
// This library is free software; you can redistribute it and/or modify it
|
// This library is free software; you can redistribute it and/or modify it
|
||||||
// under the terms of the GNU Lesser General Public License as published
|
// under the terms of the GNU Lesser General Public License as published
|
||||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||||
@@ -22,11 +22,14 @@ package com.samskivert.depot.tests;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
import com.samskivert.depot.CacheAdapter;
|
import com.samskivert.depot.CacheAdapter;
|
||||||
|
import com.samskivert.util.Tuple;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple cache adapter that stores all cached values in an in-memory map and never flushes.
|
* A simple cache adapter that stores all cached values in an in-memory map and never flushes.
|
||||||
@@ -34,17 +37,6 @@ import com.samskivert.depot.CacheAdapter;
|
|||||||
*/
|
*/
|
||||||
public class TestCacheAdapter implements CacheAdapter
|
public class TestCacheAdapter implements CacheAdapter
|
||||||
{
|
{
|
||||||
// from interface CacheAdapter
|
|
||||||
public synchronized <T> CacheAdapter.CacheBin<T> getCache (String id)
|
|
||||||
{
|
|
||||||
@SuppressWarnings("unchecked") CacheBin<T> bin = (CacheBin<T>)_bins.get(id);
|
|
||||||
if (bin == null) {
|
|
||||||
bin = new TestCacheBin<T>();
|
|
||||||
_bins.put(id, bin);
|
|
||||||
}
|
|
||||||
return bin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// from interface CacheAdapter
|
// from interface CacheAdapter
|
||||||
public void shutdown ()
|
public void shutdown ()
|
||||||
{
|
{
|
||||||
@@ -62,27 +54,36 @@ public class TestCacheAdapter implements CacheAdapter
|
|||||||
protected final T _value;
|
protected final T _value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class TestCacheBin<T> implements CacheAdapter.CacheBin<T>
|
public <T> CacheAdapter.CachedValue<T> lookup (String cacheId, Serializable key) {
|
||||||
|
// System.err.println("GET " + key + ": " + _cache.containsKey(key));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
CachedValue<T> value = (CachedValue<T>) _cache.get(
|
||||||
|
new Tuple<String, Serializable>(cacheId, key));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
public <T> void store (CacheCategory category, String cacheId, Serializable key, T value) {
|
||||||
|
// System.err.println("STORE " + key);
|
||||||
|
_cache.put(new Tuple<String, Serializable>(cacheId, key), new TestCachedValue<T>(value));
|
||||||
|
}
|
||||||
|
public void remove (String cacheId, Serializable key) {
|
||||||
|
// System.err.println("REMOVE " + key);
|
||||||
|
_cache.remove(new Tuple<String, Serializable>(cacheId, key));
|
||||||
|
}
|
||||||
|
public <T> Iterable<Tuple<Serializable, CachedValue<T>>> enumerate (String cacheId)
|
||||||
{
|
{
|
||||||
public CacheAdapter.CachedValue<T> lookup (Serializable key) {
|
// in a real implementation this would be a lazily constructed iterable
|
||||||
// System.err.println("GET " + key + ": " + _cache.containsKey(key));
|
List<Tuple<Serializable, CachedValue<T>>> result = Lists.newArrayList();
|
||||||
return _cache.get(key);
|
for (Map.Entry<Tuple<String, Serializable>, CachedValue<?>> entry: _cache.entrySet()) {
|
||||||
|
if (entry.getKey().left.equals(cacheId)) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
CachedValue<T> value = (CachedValue<T>) entry.getValue();
|
||||||
|
result.add(Tuple.newTuple(entry.getKey().right, value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public void store (Serializable key, T value) {
|
return result;
|
||||||
// System.err.println("STORE " + key);
|
|
||||||
_cache.put(key, new TestCachedValue<T>(value));
|
|
||||||
}
|
|
||||||
public void remove (Serializable key) {
|
|
||||||
// System.err.println("REMOVE " + key);
|
|
||||||
_cache.remove(key);
|
|
||||||
}
|
|
||||||
public Iterable<Serializable> enumerateKeys () {
|
|
||||||
return _cache.keySet();
|
|
||||||
}
|
|
||||||
protected Map<Serializable, CacheAdapter.CachedValue<T>> _cache =
|
|
||||||
Collections.synchronizedMap(
|
|
||||||
Maps.<Serializable, CacheAdapter.CachedValue<T>>newHashMap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Map<String, CacheBin<?>> _bins = Maps.newHashMap();
|
protected Map<Tuple<String, Serializable>, CachedValue<?>> _cache =
|
||||||
|
Collections.synchronizedMap(
|
||||||
|
Maps.<Tuple<String, Serializable>, CachedValue<?>>newHashMap());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user