Zell patch to factor caching code into pluggable CacheAdapter class. Allows us

to disable caching and/or replace it with less industrial strength system
during development.
This commit is contained in:
Michael Bayne
2007-07-26 19:07:20 +00:00
parent cf4f640ab3
commit 3628ec528d
5 changed files with 249 additions and 85 deletions
@@ -0,0 +1,77 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, 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.jdbc.depot;
import java.io.Serializable;
/**
* 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
* occur on a per-bin per-key basis.
*/
public interface CacheAdapter
{
/** The encapsulated result of a cache lookup. */
public interface CachedValue<T>
{
/** Returns the cached value, which can be null. */
public T getValue ();
}
/**
* A reference to a specific bin within the cache; this is the type where most of the actual
* caching functionality occurs.
*/
public interface CacheBin<T>
{
/**
* 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.
*/
public <T> CacheBin<T> getCache (String id);
/**
* Shut down all operations, e.g. persisting memory contents to disk.
*/
public void shutdown ();
}
@@ -0,0 +1,104 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, 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.jdbc.depot;
import java.io.Serializable;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
/**
* An implementation of {@link CacheAdapter} for ehcache.
*/
public class EHCacheAdapter
implements CacheAdapter
{
public EHCacheAdapter ()
{
_cachemgr = CacheManager.getInstance();
}
public <T> CacheBin<T> getCache (String id)
{
return new EHCacheBin<T>(id);
}
/**
* The main ehcache-bridging class, a {@link CacheBin} interface against {@link Cache}.
*/
protected class EHCacheBin<T> implements CacheBin<T>
{
// from CacheBin
public CachedValue<T> lookup (Serializable key)
{
Element hit = _cache.get(key);
if (hit == null) {
return null;
}
@SuppressWarnings("unchecked") final T value = (T) hit.getValue();
return new CachedValue<T>() {
public T getValue () {
return value;
}
};
}
// from CacheBin
public void store (Serializable key, T value)
{
_cache.put(new Element(key, value));
}
// 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)
{
_cache = _cachemgr.getCache(id);
if (_cache == null) {
_cache = new Cache(id, 5000, false, false, 600, 60);
_cachemgr.addCache(_cache);
}
}
protected Cache _cache;
}
// from CacheAdapter
public void shutdown ()
{
CacheManager.getInstance().shutdown();
}
protected CacheManager _cachemgr;
}
@@ -29,9 +29,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.ArrayUtil;
@@ -83,10 +80,9 @@ public abstract class FindAllQuery<T extends PersistentRecord>
allKeys.add(key);
// TODO: All this cache fiddling needs to move to PersistenceContext?
Cache cache = _ctx.getCache(key.getCacheId());
Element hit = cache.get(key.getCacheKey());
CacheAdapter.CachedValue<T> hit = _ctx.cacheLookup(key);
if (hit != null) {
@SuppressWarnings("unchecked") T value = (T) hit.getValue();
T value = hit.getValue();
if (value != null) {
@SuppressWarnings("unchecked") T newValue = (T) value.clone();
entities.put(key, newValue);
@@ -30,28 +30,4 @@ public class Log
{
/** We dispatch our log messages through this logger. */
public static Logger log = Logger.getLogger("com.samskivert.jdbc.depot");
/** Convenience function. */
public static void debug (String message)
{
log.fine(message);
}
/** Convenience function. */
public static void info (String message)
{
log.info(message);
}
/** Convenience function. */
public static void warning (String message)
{
log.warning(message);
}
/** Convenience function. */
public static void logStackTrace (Throwable t)
{
log.log(Level.WARNING, t.getMessage(), t);
}
}
@@ -21,25 +21,17 @@
package com.samskivert.jdbc.depot;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.samskivert.jdbc.depot.annotation.TableGenerator;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.StringUtil;
import com.samskivert.jdbc.ConnectionProvider;
@@ -49,6 +41,10 @@ import com.samskivert.jdbc.LiaisonRegistry;
import com.samskivert.jdbc.MySQLLiaison;
import com.samskivert.jdbc.PostgreSQLLiaison;
import com.samskivert.jdbc.depot.annotation.TableGenerator;
import static com.samskivert.jdbc.depot.Log.log;
/**
* Defines a scope in which global annotations are shared.
*/
@@ -126,11 +122,23 @@ public class PersistenceContext
* connection.
*/
public PersistenceContext (String ident, ConnectionProvider conprov)
{
this(ident, conprov, null);
}
/**
* Creates a persistence context that will use the supplied provider to obtain JDBC
* connections.
*
* @param ident the identifier to provide to the connection provider when requesting a
* connection.
*/
public PersistenceContext (String ident, ConnectionProvider conprov, CacheAdapter adapter)
{
_ident = ident;
_conprov = conprov;
_cachemgr = CacheManager.getInstance();
_liaison = LiaisonRegistry.getLiaison(conprov.getURL(ident));
_cache = adapter;
}
/**
@@ -204,21 +212,18 @@ public class PersistenceContext
{
CacheKey key = query.getCacheKey();
// if there is a cache key, check the cache
if (key != null) {
Cache cache = getCache(key.getCacheId());
if (cache != null) {
Element cacheHit = cache.get(key.getCacheKey());
if (cacheHit != null) {
Log.debug("invoke: cache hit [hit=" + cacheHit + "]");
@SuppressWarnings("unchecked") T value = (T) cacheHit.getValue();
value = query.transformCacheHit(key, value);
if (value != null) {
return value;
}
Log.debug("invoke: transformCacheHit returned null; rejecting cached value.");
if (key != null && _cache != null) {
CacheAdapter.CachedValue<T> cacheHit = cacheLookup(key);
if (cacheHit != null) {
log.fine("invoke: cache hit [hit=" + cacheHit + "]");
T value = cacheHit.getValue();
value = query.transformCacheHit(key, value);
if (value != null) {
return value;
}
log.fine("invoke: transformCacheHit returned null; rejecting cached value.");
}
Log.debug("invoke: cache miss [key=" + key + "]");
log.fine("invoke: cache miss [key=" + key + "]");
}
// otherwise, perform the query
@SuppressWarnings("unchecked") T result = (T) invoke(query, null, true);
@@ -242,16 +247,15 @@ public class PersistenceContext
}
/**
* Returns the {@link Cache} for the given cache id, or creates one if necessary.
* Looks up an entry in the cache by the given key.
*/
public Cache getCache (String cacheId)
public <T> CacheAdapter.CachedValue<T> cacheLookup (CacheKey key)
{
Cache cache = _cachemgr.getCache(cacheId);
if (cache == null) {
cache = new Cache(cacheId, 5000, false, false, 600, 60);
_cachemgr.addCache(cache);
if (_cache == null) {
return null;
}
return cache;
CacheAdapter.CacheBin<T> bin = _cache.getCache(key.getCacheId());
return bin.lookup(key.getCacheKey());
}
/**
@@ -259,27 +263,29 @@ public class PersistenceContext
*/
public <T> void cacheStore (CacheKey key, T entry)
{
if (_cache == null) {
return;
}
if (key == null) {
Log.warning("Cache key must not be null [entry=" + entry + "]");
log.warning("Cache key must not be null [entry=" + entry + "]");
Thread.dumpStack();
return;
}
Log.debug("cacheStore: entry [key=" + key + ", value=" + entry + "]");
log.fine("cacheStore: entry [key=" + key + ", value=" + entry + "]");
// find the old entry, if any
Cache cache = getCache(key.getCacheId());
Element element = cache.get(key.getCacheKey());
CacheAdapter.CacheBin<T> bin = _cache.getCache(key.getCacheId());
CacheAdapter.CachedValue element = bin.lookup(key.getCacheKey());
@SuppressWarnings("unchecked") T oldEntry =
(element != null ? (T) element.getValue() : null);
// update the cache
cache.put(new Element(key.getCacheKey(), entry));
bin.store(key.getCacheKey(), entry);
// then do cache invalidations
Set<CacheListener<?>> listeners = _listenerSets.get(key.getCacheId());
if (listeners != null && listeners.size() > 0) {
for (CacheListener<?> listener : listeners) {
Log.debug("cacheInvalidate: cascading [listener=" + listener + "]");
log.fine("cacheInvalidate: cascading [listener=" + listener + "]");
@SuppressWarnings("unchecked")
CacheListener<T> casted = (CacheListener<T>)listener;
casted.entryCached(key, entry, oldEntry);
@@ -294,7 +300,7 @@ public class PersistenceContext
public void cacheInvalidate (CacheKey key)
{
if (key == null) {
Log.warning("Cache key to invalidate must not be null.");
log.warning("Cache key to invalidate must not be null.");
Thread.dumpStack();
} else {
cacheInvalidate(key.getCacheId(), key.getCacheKey());
@@ -316,23 +322,26 @@ public class PersistenceContext
*/
public <T extends Serializable> void cacheInvalidate (String cacheId, Serializable cacheKey)
{
Log.debug("cacheInvalidate: entry [cacheId=" + cacheId + ", cacheKey=" + cacheKey + "]");
if (_cache == null) {
return;
}
log.fine("cacheInvalidate: entry [cacheId=" + cacheId + ", cacheKey=" + cacheKey + "]");
Cache cache = getCache(cacheId);
Element element = cache.get(cacheKey);
CacheAdapter.CacheBin<T> bin = _cache.getCache(cacheId);
CacheAdapter.CachedValue<T> element = bin.lookup(cacheKey);
if (element == null) {
return;
}
// find the old entry, if any
@SuppressWarnings("unchecked") T oldEntry = (T) element.getValue();
T oldEntry = element.getValue();
if (oldEntry != null) {
// if there was one, do (possibly cascading) cache invalidations
Set<CacheListener<?>> listeners = _listenerSets.get(cacheId);
if (listeners != null && listeners.size() > 0) {
CacheKey key = new SimpleCacheKey(cacheId, cacheKey);
for (CacheListener<?> listener : listeners) {
Log.debug("cacheInvalidate: cascading [listener=" + listener + "]");
log.fine("cacheInvalidate: cascading [listener=" + listener + "]");
@SuppressWarnings("unchecked") CacheListener<T> casted =
(CacheListener<T>)listener;
casted.entryInvalidated(key, oldEntry);
@@ -341,8 +350,8 @@ public class PersistenceContext
}
// then evict the keyed entry, if needed
Log.debug("cacheInvalidate: evicting [cacheKey=" + cacheKey + "]");
cache.remove(cacheKey);
log.fine("cacheInvalidate: evicting [cacheKey=" + cacheKey + "]");
bin.remove(cacheKey);
}
/**
@@ -360,14 +369,15 @@ public class PersistenceContext
*/
public <T extends Serializable> void cacheTraverse (String cacheId, CacheTraverser<T> filter)
{
Cache cache = getCache(cacheId);
if (cache != null) {
for (Object key : cache.getKeys()) {
Serializable sKey = (Serializable) key;
Element element = cache.get(sKey);
if (_cache == null) {
return;
}
CacheAdapter.CacheBin<T> bin = _cache.getCache(cacheId);
if (bin != null) {
for (Object key : bin.enumerateKeys()) {
CacheAdapter.CachedValue<T> element = bin.lookup((Serializable) key);
if (element != null) {
@SuppressWarnings("unchecked") T value = (T) element.getValue();
filter.visitCacheEntry(this, cacheId, sKey, value);
filter.visitCacheEntry(this, cacheId, (Serializable) key, element.getValue());
}
}
}
@@ -473,8 +483,7 @@ public class PersistenceContext
// outer exception; if I want a fucking stack trace, I'll call
// printStackTrace() thanksverymuch
String msg = StringUtil.split(String.valueOf(sqe), "\n")[0];
Log.info("Transient failure executing operation, " +
"retrying [error=" + msg + "].");
log.info("Transient failure executing operation, retrying [error=" + msg + "].");
} else {
String msg = isReadOnlyQuery ? "Query failure " + query
@@ -492,9 +501,11 @@ public class PersistenceContext
protected String _ident;
protected ConnectionProvider _conprov;
protected CacheManager _cachemgr;
protected DatabaseLiaison _liaison;
/** The object through which all our caching is relayed, or null, for no caching. */
protected CacheAdapter _cache;
protected Map<String, Set<CacheListener<?>>> _listenerSets =
new HashMap<String, Set<CacheListener<?>>>();