Added support for clearing caches.
This commit is contained in:
@@ -58,6 +58,14 @@ public interface CacheAdapter
|
||||
*/
|
||||
public <T> Iterable<Serializable> enumerate (String cacheId);
|
||||
|
||||
/**
|
||||
* Clears the cache with the specified name.
|
||||
*
|
||||
* @param localOnly if true, only clear the cache on the local node, do not broadcast
|
||||
* instructions for all distributed nodes to clear this cache as well.
|
||||
*/
|
||||
public void clear (String cacheId, boolean localOnly);
|
||||
|
||||
/**
|
||||
* Shut down all operations, e.g. persisting memory contents to disk.
|
||||
*/
|
||||
|
||||
@@ -240,50 +240,7 @@ public abstract class DepotRepository
|
||||
Class<T> type, CacheStrategy cache, Iterable<? extends QueryClause> clauses)
|
||||
throws DatabaseException
|
||||
{
|
||||
DepotMarshaller<T> marsh = _ctx.getMarshaller(type);
|
||||
|
||||
switch (cache) {
|
||||
case LONG_KEYS: case SHORT_KEYS: case BEST: case RECORDS:
|
||||
String reason = null;
|
||||
if (marsh.getTableName() == null) {
|
||||
reason = type + " is computed";
|
||||
|
||||
} else if (!marsh.hasPrimaryKey()) {
|
||||
reason = type + " has no primary key";
|
||||
|
||||
} else {
|
||||
for (QueryClause clause : clauses) {
|
||||
if (clause instanceof FieldOverride) {
|
||||
reason = "query uses a FieldOverride clause";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cache == CacheStrategy.BEST) {
|
||||
cache = (reason != null) ? CacheStrategy.NONE : CacheStrategy.SHORT_KEYS;
|
||||
} else if (reason != null) {
|
||||
// if user explicitly asked for a strategy we can't do, protest
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot use " + cache + " strategy because " + reason);
|
||||
}
|
||||
break;
|
||||
|
||||
case NONE: case CONTENTS:
|
||||
break; // NONE and CONTENTS can always be used.
|
||||
}
|
||||
|
||||
if (!_ctx.isUsingCache()) {
|
||||
cache = CacheStrategy.NONE;
|
||||
}
|
||||
|
||||
switch (cache) {
|
||||
case SHORT_KEYS: case LONG_KEYS: case RECORDS:
|
||||
return _ctx.invoke(new FindAllQuery.WithCache<T>(_ctx, type, clauses, cache));
|
||||
|
||||
default:
|
||||
return _ctx.invoke(new FindAllQuery.Explicitly<T>(
|
||||
_ctx, type, clauses, cache == CacheStrategy.CONTENTS));
|
||||
}
|
||||
return _ctx.invoke(FindAllQuery.newCachedFullRecordQuery(_ctx, type, cache, clauses));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -173,6 +173,15 @@ public class EHCacheAdapter
|
||||
return result;
|
||||
}
|
||||
|
||||
// from CacheAdapter
|
||||
public void clear (String cacheId, boolean localOnly)
|
||||
{
|
||||
EHCacheBin<?> bin = _bins.get(cacheId);
|
||||
if (bin != null) {
|
||||
bin.getCache().removeAll(localOnly);
|
||||
}
|
||||
}
|
||||
|
||||
// from CacheAdapter
|
||||
public void shutdown ()
|
||||
{
|
||||
|
||||
@@ -425,6 +425,33 @@ public class PersistenceContext
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the cache for the specified persistent record be cleared. Note that this
|
||||
* clears only the <em>by-primary-key</em> cache for the index in question. It does not clear
|
||||
* the <em>keyset</em> or <em>contents</em> query caches. Use {@link Query#clearCache} to clear
|
||||
* these caches.
|
||||
*
|
||||
* @param localOnly if true, only the cache in this JVM will be cleared, no broadcast message
|
||||
* will be sent to instruct all distributed nodes to also clear this cache.
|
||||
*/
|
||||
public void cacheClear (Class<? extends PersistentRecord> pClass, boolean localOnly)
|
||||
{
|
||||
cacheClear(pClass.getName(), localOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the cache with the specified id be cleared.
|
||||
*
|
||||
* @param localOnly if true, only the cache in this JVM will be cleared, no broadcast message
|
||||
* will be sent to instruct all distributed nodes to also clear this cache.
|
||||
*/
|
||||
public void cacheClear (String cacheId, boolean localOnly)
|
||||
{
|
||||
if (_cache != null) {
|
||||
_cache.clear(cacheId, localOnly);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new cache listener with the cache associated with the given class.
|
||||
*/
|
||||
|
||||
@@ -555,6 +555,37 @@ public class Query<T extends PersistentRecord>
|
||||
return _repo.deleteAll(_pclass, _where, _limit, invalidator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cache for the query configured by this instance. If the query would not result in
|
||||
* cache usage for whatever reason, this will NOOP.
|
||||
*
|
||||
* <p> Note: Depot uses numerous caches, so it is important to know what you are doing. If the
|
||||
* records fetched by this query have a primary key, Depot performs a two-phase query where the
|
||||
* primary keys for the records that satisify this query are first fetched and stored in a
|
||||
* <em>keyset</em> cache, then the actual records needed to satisfy the query are fetched using
|
||||
* the <em>by-primary-key</em> cache. This method will only clear the <em>keyset</em> cache.
|
||||
* That may be sufficient for your purposes, or you may need to call {@link
|
||||
* PersistenceContext#cacheClear(Class,boolean)} to clear the <em>by-primary-key</em> cache as
|
||||
* well. If the record in question does not define a primary key, Depot will use a
|
||||
* <em>contents</em> cache to store the entire records fetched as a result of executing this
|
||||
* query. In that case, calling this method is sufficient to ensure that no cached data will be
|
||||
* used to fulfill subsequent similarly configured queries. </p>
|
||||
*
|
||||
* <p> Finally, note that query configuration other than {@link #cache} (i.e. {@link #limit},
|
||||
* {@link #groupBy}, {@link #join}, etc.) will have no influence on this operation. </p>
|
||||
*
|
||||
* @param localOnly if true, only the cache in this JVM will be cleared, no broadcast message
|
||||
* will be sent to instruct all distributed nodes to also clear this cache.
|
||||
*/
|
||||
public void clearCache (boolean localOnly)
|
||||
{
|
||||
String cacheId = FindAllQuery.newCachedFullRecordQuery(
|
||||
_ctx, _pclass, _cache, getClauses()).getCacheId();
|
||||
if (cacheId != null) {
|
||||
_ctx.cacheClear(cacheId, localOnly);
|
||||
}
|
||||
}
|
||||
|
||||
protected Query (PersistenceContext ctx, DepotRepository repo, Class<T> pclass)
|
||||
{
|
||||
_ctx = ctx;
|
||||
|
||||
@@ -47,24 +47,66 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
|
||||
extends Fetcher<List<R>>
|
||||
{
|
||||
/**
|
||||
* A base class for queries that fetch a full record at a time.
|
||||
* A base class for cached queries that fetch a full record at a time.
|
||||
*/
|
||||
public static abstract class FullRecordQuery<T extends PersistentRecord>
|
||||
extends FindAllQuery<T, T>
|
||||
public static abstract class CachedFullRecordQuery<T extends PersistentRecord>
|
||||
extends FullRecordQuery<T>
|
||||
{
|
||||
protected FullRecordQuery (PersistenceContext ctx, Class<T> type) {
|
||||
super(type, ctx.getMarshaller(type), new CloningCloner<T>());
|
||||
_dmarsh = ctx.getMarshaller(type);
|
||||
protected CachedFullRecordQuery (PersistenceContext ctx, Class<T> type) {
|
||||
super(ctx, type);
|
||||
}
|
||||
|
||||
protected DepotMarshaller<T> _dmarsh;
|
||||
/**
|
||||
* Returns the id of the cache in which this query's results will be stored, or null if the
|
||||
* query is not configured to use a cache.
|
||||
*/
|
||||
public String getCacheId () {
|
||||
return (_qkey == null) ? null : _qkey.getCacheId();
|
||||
}
|
||||
|
||||
protected SimpleCacheKey _qkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* The two-pass collection query implementation. See {@link DepotRepository#findAll} for
|
||||
* details.
|
||||
*/
|
||||
public static class WithCache<T extends PersistentRecord> extends FullRecordQuery<T>
|
||||
public static class WithKeys<T extends PersistentRecord> extends FullRecordQuery<T>
|
||||
{
|
||||
public WithKeys (PersistenceContext ctx, Iterable<Key<T>> keys)
|
||||
throws DatabaseException
|
||||
{
|
||||
super(ctx, keys.iterator().next().getPersistentClass());
|
||||
_keys = keys;
|
||||
}
|
||||
|
||||
@Override // from Fetcher
|
||||
public List<T> getCachedResult (PersistenceContext ctx)
|
||||
{
|
||||
// look up what we can from the cache
|
||||
_fetchKeys = loadFromCache(ctx, _keys, _entities);
|
||||
|
||||
// if we found everything, we can just return our result straight away, yay!
|
||||
return _fetchKeys.isEmpty() ? resolve(_keys, _entities) : null;
|
||||
}
|
||||
|
||||
// from Fetcher
|
||||
public List<T> invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException
|
||||
{
|
||||
return loadAndResolve(ctx, conn, _keys, _fetchKeys, _entities, null);
|
||||
}
|
||||
|
||||
protected Iterable<Key<T>> _keys;
|
||||
protected Set<Key<T>> _fetchKeys;
|
||||
protected Map<Key<T>, T> _entities = Maps.newHashMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* The two-pass collection query implementation. See {@link DepotRepository#findAll} for
|
||||
* details.
|
||||
*/
|
||||
public static class WithCache<T extends PersistentRecord> extends CachedFullRecordQuery<T>
|
||||
{
|
||||
public WithCache (PersistenceContext ctx, Class<T> type,
|
||||
Iterable<? extends QueryClause> clauses, CacheStrategy strategy)
|
||||
@@ -94,7 +136,6 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected cache strategy: " + strategy);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override // from Fetcher
|
||||
@@ -148,7 +189,6 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
|
||||
return loadAndResolve(ctx, conn, _keys, _fetchKeys, _entities, stmtString);
|
||||
}
|
||||
|
||||
protected SimpleCacheKey _qkey;
|
||||
protected CacheCategory _category;
|
||||
protected SelectClause _select;
|
||||
protected KeySet<T> _keys;
|
||||
@@ -156,60 +196,20 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
|
||||
protected Map<Key<T>, T> _entities = Maps.newHashMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* The two-pass collection query implementation. See {@link DepotRepository#findAll} for
|
||||
* details.
|
||||
*/
|
||||
public static class WithKeys<T extends PersistentRecord> extends FullRecordQuery<T>
|
||||
{
|
||||
public WithKeys (PersistenceContext ctx, Iterable<Key<T>> keys)
|
||||
throws DatabaseException
|
||||
{
|
||||
super(ctx, keys.iterator().next().getPersistentClass());
|
||||
_keys = keys;
|
||||
}
|
||||
|
||||
@Override // from Fetcher
|
||||
public List<T> getCachedResult (PersistenceContext ctx)
|
||||
{
|
||||
// look up what we can from the cache
|
||||
_fetchKeys = loadFromCache(ctx, _keys, _entities);
|
||||
|
||||
// if we found everything, we can just return our result straight away, yay!
|
||||
return _fetchKeys.isEmpty() ? resolve(_keys, _entities) : null;
|
||||
}
|
||||
|
||||
// from Fetcher
|
||||
public List<T> invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException
|
||||
{
|
||||
return loadAndResolve(ctx, conn, _keys, _fetchKeys, _entities, null);
|
||||
}
|
||||
|
||||
protected Iterable<Key<T>> _keys;
|
||||
protected Set<Key<T>> _fetchKeys;
|
||||
protected Map<Key<T>, T> _entities = Maps.newHashMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* The single-pass collection query implementation. See {@link DepotRepository#findAll} for
|
||||
* details.
|
||||
*/
|
||||
public static class Explicitly<T extends PersistentRecord> extends FullRecordQuery<T>
|
||||
public static class Explicitly<T extends PersistentRecord> extends CachedFullRecordQuery<T>
|
||||
{
|
||||
public Explicitly (PersistenceContext ctx, Class<T> type,
|
||||
Iterable<? extends QueryClause> clauses, boolean cachedContents)
|
||||
throws DatabaseException
|
||||
{
|
||||
super(ctx, type);
|
||||
|
||||
_select = new SelectClause(type, _dmarsh.getSelections(), clauses);
|
||||
|
||||
if (cachedContents) {
|
||||
_qkey = new SimpleCacheKey(_dmarsh.getTableName() + "Contents", _select.toString());
|
||||
} else {
|
||||
_qkey = null;
|
||||
}
|
||||
_qkey = !cachedContents ? null :
|
||||
new SimpleCacheKey(_dmarsh.getTableName() + "Contents", _select.toString());
|
||||
}
|
||||
|
||||
@Override // from Fetcher
|
||||
@@ -246,7 +246,6 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
|
||||
}
|
||||
|
||||
protected SelectClause _select;
|
||||
protected SimpleCacheKey _qkey;
|
||||
}
|
||||
|
||||
public static class Projection<T extends PersistentRecord,R>
|
||||
@@ -286,47 +285,51 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
|
||||
protected DepotTypes _types;
|
||||
}
|
||||
|
||||
protected static class ProjectionQueryMarshaller<T extends PersistentRecord,R>
|
||||
implements QueryMarshaller<T,R>
|
||||
public static <T extends PersistentRecord> CachedFullRecordQuery<T> newCachedFullRecordQuery (
|
||||
PersistenceContext ctx, Class<T> type, CacheStrategy strategy,
|
||||
Iterable<? extends QueryClause> clauses)
|
||||
{
|
||||
public ProjectionQueryMarshaller (Projector<T,R> cset, DepotTypes types) {
|
||||
_cset = cset;
|
||||
_types = types;
|
||||
}
|
||||
DepotMarshaller<T> marsh = ctx.getMarshaller(type);
|
||||
|
||||
public String getTableName () {
|
||||
return _types.getTableName(_cset.ptype);
|
||||
}
|
||||
switch (strategy) {
|
||||
case LONG_KEYS: case SHORT_KEYS: case BEST: case RECORDS:
|
||||
String reason = null;
|
||||
if (marsh.getTableName() == null) {
|
||||
reason = type + " is computed";
|
||||
|
||||
public SQLExpression<?>[] getSelections () {
|
||||
return _cset.selexps;
|
||||
}
|
||||
} else if (!marsh.hasPrimaryKey()) {
|
||||
reason = type + " has no primary key";
|
||||
|
||||
public Key<T> getPrimaryKey (Object object) {
|
||||
return _types.getMarshaller(_cset.ptype).getPrimaryKey(object);
|
||||
}
|
||||
|
||||
public R createObject (ResultSet rs) throws SQLException {
|
||||
Object[] data = new Object[_cset.selexps.length];
|
||||
for (int ii = 0; ii < data.length; ii++) {
|
||||
SQLExpression<?> exp = _cset.selexps[ii];
|
||||
if (exp instanceof ColumnExp<?>) {
|
||||
ColumnExp<?> col = (ColumnExp<?>)exp;
|
||||
data[ii] = _types.getMarshaller(col.getPersistentClass()).
|
||||
getFieldMarshaller(col.name).getFromSet(rs, ii+1);
|
||||
} else {
|
||||
// TEMP: in the case of selecting computed expressions, we rely on the types to
|
||||
// be correct by construction; TODO: this will probably bite us when JDBC
|
||||
// drivers choose Long instead of Integer or whatnot, so we'll need to set up
|
||||
// more complex machinery
|
||||
data[ii] = rs.getObject(ii+1);
|
||||
} else {
|
||||
for (QueryClause clause : clauses) {
|
||||
if (clause instanceof FieldOverride) {
|
||||
reason = "query uses a FieldOverride clause";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return _cset.createObject(data);
|
||||
if (strategy == CacheStrategy.BEST) {
|
||||
strategy = (reason != null) ? CacheStrategy.NONE : CacheStrategy.SHORT_KEYS;
|
||||
} else if (reason != null) {
|
||||
// if user explicitly asked for a strategy we can't do, protest
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot use " + strategy + " strategy because " + reason);
|
||||
}
|
||||
break;
|
||||
|
||||
case NONE: case CONTENTS:
|
||||
break; // NONE and CONTENTS can always be used.
|
||||
}
|
||||
|
||||
protected Projector<T,R> _cset;
|
||||
protected DepotTypes _types;
|
||||
// if we're not using a cache, then skip all of the above
|
||||
strategy = ctx.isUsingCache() ? strategy : CacheStrategy.NONE;
|
||||
|
||||
switch (strategy) {
|
||||
case SHORT_KEYS: case LONG_KEYS: case RECORDS:
|
||||
return new WithCache<T>(ctx, type, clauses, strategy);
|
||||
default:
|
||||
return new Explicitly<T>(ctx, type, clauses, strategy == CacheStrategy.CONTENTS);
|
||||
}
|
||||
}
|
||||
|
||||
// from Fetcher
|
||||
@@ -457,6 +460,62 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
|
||||
return builder.append(")").toString();
|
||||
}
|
||||
|
||||
/** A base class for queries that fetch a full record at a time. */
|
||||
protected static abstract class FullRecordQuery<T extends PersistentRecord>
|
||||
extends FindAllQuery<T, T>
|
||||
{
|
||||
protected FullRecordQuery (PersistenceContext ctx, Class<T> type) {
|
||||
super(type, ctx.getMarshaller(type), new CloningCloner<T>());
|
||||
_dmarsh = ctx.getMarshaller(type);
|
||||
}
|
||||
|
||||
protected DepotMarshaller<T> _dmarsh;
|
||||
}
|
||||
|
||||
/** Helper for {@link Projection}. */
|
||||
protected static class ProjectionQueryMarshaller<T extends PersistentRecord,R>
|
||||
implements QueryMarshaller<T,R>
|
||||
{
|
||||
public ProjectionQueryMarshaller (Projector<T,R> cset, DepotTypes types) {
|
||||
_cset = cset;
|
||||
_types = types;
|
||||
}
|
||||
|
||||
public String getTableName () {
|
||||
return _types.getTableName(_cset.ptype);
|
||||
}
|
||||
|
||||
public SQLExpression<?>[] getSelections () {
|
||||
return _cset.selexps;
|
||||
}
|
||||
|
||||
public Key<T> getPrimaryKey (Object object) {
|
||||
return _types.getMarshaller(_cset.ptype).getPrimaryKey(object);
|
||||
}
|
||||
|
||||
public R createObject (ResultSet rs) throws SQLException {
|
||||
Object[] data = new Object[_cset.selexps.length];
|
||||
for (int ii = 0; ii < data.length; ii++) {
|
||||
SQLExpression<?> exp = _cset.selexps[ii];
|
||||
if (exp instanceof ColumnExp<?>) {
|
||||
ColumnExp<?> col = (ColumnExp<?>)exp;
|
||||
data[ii] = _types.getMarshaller(col.getPersistentClass()).
|
||||
getFieldMarshaller(col.name).getFromSet(rs, ii+1);
|
||||
} else {
|
||||
// TEMP: in the case of selecting computed expressions, we rely on the types to
|
||||
// be correct by construction; TODO: this will probably bite us when JDBC
|
||||
// drivers choose Long instead of Integer or whatnot, so we'll need to set up
|
||||
// more complex machinery
|
||||
data[ii] = rs.getObject(ii+1);
|
||||
}
|
||||
}
|
||||
return _cset.createObject(data);
|
||||
}
|
||||
|
||||
protected Projector<T,R> _cset;
|
||||
protected DepotTypes _types;
|
||||
}
|
||||
|
||||
// we have to factor out cloning data for cache storage so that we can support storing
|
||||
// immutable data (like Integer and String) which need not and cannot be cloned, versus mutable
|
||||
// data (like PersistentRecords and Tuples) which must be cloned
|
||||
|
||||
@@ -6,6 +6,7 @@ package com.samskivert.depot;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -20,6 +21,57 @@ import com.samskivert.util.Tuple;
|
||||
*/
|
||||
public class TestCacheAdapter implements CacheAdapter
|
||||
{
|
||||
// from interface CacheAdapter
|
||||
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;
|
||||
}
|
||||
|
||||
// from interface CacheAdapter
|
||||
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));
|
||||
}
|
||||
|
||||
// from interface CacheAdapter
|
||||
public void remove (String cacheId, Serializable key)
|
||||
{
|
||||
// System.err.println("REMOVE " + key);
|
||||
_cache.remove(new Tuple<String, Serializable>(cacheId, key));
|
||||
}
|
||||
|
||||
// from interface CacheAdapter
|
||||
public <T> Iterable<Serializable> enumerate (String cacheId)
|
||||
{
|
||||
// in a real implementation this would be a lazily constructed iterable
|
||||
List<Serializable> result = Lists.newArrayList();
|
||||
for (Map.Entry<Tuple<String, Serializable>, CachedValue<?>> entry: _cache.entrySet()) {
|
||||
if (entry.getKey().left.equals(cacheId)) {
|
||||
result.add(entry.getKey().right);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// from interface CacheAdapter
|
||||
public void clear (String cacheId, boolean localOnly)
|
||||
{
|
||||
synchronized (_cache) {
|
||||
for (Iterator<Tuple<String, Serializable>> iter = _cache.keySet().iterator();
|
||||
iter.hasNext(); ) {
|
||||
Tuple<String, Serializable> key = iter.next();
|
||||
if (key.left.equals(cacheId)) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// from interface CacheAdapter
|
||||
public void shutdown ()
|
||||
{
|
||||
@@ -37,33 +89,6 @@ public class TestCacheAdapter implements CacheAdapter
|
||||
protected final T _value;
|
||||
}
|
||||
|
||||
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<Serializable> enumerate (String cacheId)
|
||||
{
|
||||
// in a real implementation this would be a lazily constructed iterable
|
||||
List<Serializable> result = Lists.newArrayList();
|
||||
for (Map.Entry<Tuple<String, Serializable>, CachedValue<?>> entry: _cache.entrySet()) {
|
||||
if (entry.getKey().left.equals(cacheId)) {
|
||||
result.add(entry.getKey().right);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Map<Tuple<String, Serializable>, CachedValue<?>> _cache =
|
||||
Collections.synchronizedMap(
|
||||
Maps.<Tuple<String, Serializable>, CachedValue<?>>newHashMap());
|
||||
|
||||
Reference in New Issue
Block a user