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;
}
@@ -3,7 +3,7 @@
// //
// samskivert library - useful routines for java programs // samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell // Copyright (C) 2006-2007 Michael Bayne, 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
@@ -29,9 +29,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import com.samskivert.io.PersistenceException; import com.samskivert.io.PersistenceException;
import com.samskivert.util.ArrayUtil; import com.samskivert.util.ArrayUtil;
@@ -83,10 +80,9 @@ public abstract class FindAllQuery<T extends PersistentRecord>
allKeys.add(key); allKeys.add(key);
// TODO: All this cache fiddling needs to move to PersistenceContext? // TODO: All this cache fiddling needs to move to PersistenceContext?
Cache cache = _ctx.getCache(key.getCacheId()); CacheAdapter.CachedValue<T> hit = _ctx.cacheLookup(key);
Element hit = cache.get(key.getCacheKey());
if (hit != null) { if (hit != null) {
@SuppressWarnings("unchecked") T value = (T) hit.getValue(); T value = hit.getValue();
if (value != null) { if (value != null) {
@SuppressWarnings("unchecked") T newValue = (T) value.clone(); @SuppressWarnings("unchecked") T newValue = (T) value.clone();
entities.put(key, newValue); entities.put(key, newValue);
@@ -30,28 +30,4 @@ public class Log
{ {
/** We dispatch our log messages through this logger. */ /** We dispatch our log messages through this logger. */
public static Logger log = Logger.getLogger("com.samskivert.jdbc.depot"); 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; package com.samskivert.jdbc.depot;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import com.samskivert.jdbc.depot.annotation.TableGenerator;
import java.io.Serializable; import java.io.Serializable;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; 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.io.PersistenceException;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.samskivert.jdbc.ConnectionProvider; import com.samskivert.jdbc.ConnectionProvider;
@@ -49,6 +41,10 @@ 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.jdbc.depot.annotation.TableGenerator;
import static com.samskivert.jdbc.depot.Log.log;
/** /**
* Defines a scope in which global annotations are shared. * Defines a scope in which global annotations are shared.
*/ */
@@ -126,11 +122,23 @@ public class PersistenceContext
* connection. * connection.
*/ */
public PersistenceContext (String ident, ConnectionProvider conprov) 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; _ident = ident;
_conprov = conprov; _conprov = conprov;
_cachemgr = CacheManager.getInstance();
_liaison = LiaisonRegistry.getLiaison(conprov.getURL(ident)); _liaison = LiaisonRegistry.getLiaison(conprov.getURL(ident));
_cache = adapter;
} }
/** /**
@@ -204,21 +212,18 @@ public class PersistenceContext
{ {
CacheKey key = query.getCacheKey(); CacheKey key = query.getCacheKey();
// if there is a cache key, check the cache // if there is a cache key, check the cache
if (key != null) { if (key != null && _cache != null) {
Cache cache = getCache(key.getCacheId()); CacheAdapter.CachedValue<T> cacheHit = cacheLookup(key);
if (cache != null) { if (cacheHit != null) {
Element cacheHit = cache.get(key.getCacheKey()); log.fine("invoke: cache hit [hit=" + cacheHit + "]");
if (cacheHit != null) { T value = cacheHit.getValue();
Log.debug("invoke: cache hit [hit=" + cacheHit + "]"); value = query.transformCacheHit(key, value);
@SuppressWarnings("unchecked") T value = (T) cacheHit.getValue(); if (value != null) {
value = query.transformCacheHit(key, value); return value;
if (value != null) {
return value;
}
Log.debug("invoke: transformCacheHit returned null; rejecting cached 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 // otherwise, perform the query
@SuppressWarnings("unchecked") T result = (T) invoke(query, null, true); @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) {
if (cache == null) { return null;
cache = new Cache(cacheId, 5000, false, false, 600, 60);
_cachemgr.addCache(cache);
} }
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) public <T> void cacheStore (CacheKey key, T entry)
{ {
if (_cache == null) {
return;
}
if (key == null) { 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(); Thread.dumpStack();
return; return;
} }
Log.debug("cacheStore: entry [key=" + key + ", value=" + entry + "]"); log.fine("cacheStore: entry [key=" + key + ", value=" + entry + "]");
// find the old entry, if any CacheAdapter.CacheBin<T> bin = _cache.getCache(key.getCacheId());
Cache cache = getCache(key.getCacheId()); CacheAdapter.CachedValue element = bin.lookup(key.getCacheKey());
Element element = cache.get(key.getCacheKey());
@SuppressWarnings("unchecked") T oldEntry = @SuppressWarnings("unchecked") T oldEntry =
(element != null ? (T) element.getValue() : null); (element != null ? (T) element.getValue() : null);
// update the cache // update the cache
cache.put(new Element(key.getCacheKey(), entry)); bin.store(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());
if (listeners != null && listeners.size() > 0) { if (listeners != null && listeners.size() > 0) {
for (CacheListener<?> listener : listeners) { for (CacheListener<?> listener : listeners) {
Log.debug("cacheInvalidate: cascading [listener=" + listener + "]"); log.fine("cacheInvalidate: cascading [listener=" + listener + "]");
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
CacheListener<T> casted = (CacheListener<T>)listener; CacheListener<T> casted = (CacheListener<T>)listener;
casted.entryCached(key, entry, oldEntry); casted.entryCached(key, entry, oldEntry);
@@ -294,7 +300,7 @@ public class PersistenceContext
public void cacheInvalidate (CacheKey key) public void cacheInvalidate (CacheKey key)
{ {
if (key == null) { 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(); Thread.dumpStack();
} else { } else {
cacheInvalidate(key.getCacheId(), key.getCacheKey()); cacheInvalidate(key.getCacheId(), key.getCacheKey());
@@ -316,23 +322,26 @@ public class PersistenceContext
*/ */
public <T extends Serializable> void cacheInvalidate (String cacheId, Serializable cacheKey) 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); CacheAdapter.CacheBin<T> bin = _cache.getCache(cacheId);
Element element = cache.get(cacheKey); CacheAdapter.CachedValue<T> element = bin.lookup(cacheKey);
if (element == null) { if (element == null) {
return; return;
} }
// find the old entry, if any // find the old entry, if any
@SuppressWarnings("unchecked") T oldEntry = (T) element.getValue(); T oldEntry = element.getValue();
if (oldEntry != null) { if (oldEntry != null) {
// if there was one, do (possibly cascading) cache invalidations // if there was one, do (possibly cascading) cache invalidations
Set<CacheListener<?>> listeners = _listenerSets.get(cacheId); Set<CacheListener<?>> listeners = _listenerSets.get(cacheId);
if (listeners != null && listeners.size() > 0) { if (listeners != null && listeners.size() > 0) {
CacheKey key = new SimpleCacheKey(cacheId, cacheKey); CacheKey key = new SimpleCacheKey(cacheId, cacheKey);
for (CacheListener<?> listener : listeners) { for (CacheListener<?> listener : listeners) {
Log.debug("cacheInvalidate: cascading [listener=" + listener + "]"); log.fine("cacheInvalidate: cascading [listener=" + listener + "]");
@SuppressWarnings("unchecked") CacheListener<T> casted = @SuppressWarnings("unchecked") CacheListener<T> casted =
(CacheListener<T>)listener; (CacheListener<T>)listener;
casted.entryInvalidated(key, oldEntry); casted.entryInvalidated(key, oldEntry);
@@ -341,8 +350,8 @@ public class PersistenceContext
} }
// then evict the keyed entry, if needed // then evict the keyed entry, if needed
Log.debug("cacheInvalidate: evicting [cacheKey=" + cacheKey + "]"); log.fine("cacheInvalidate: evicting [cacheKey=" + cacheKey + "]");
cache.remove(cacheKey); bin.remove(cacheKey);
} }
/** /**
@@ -360,14 +369,15 @@ public class PersistenceContext
*/ */
public <T extends Serializable> void cacheTraverse (String cacheId, CacheTraverser<T> filter) public <T extends Serializable> void cacheTraverse (String cacheId, CacheTraverser<T> filter)
{ {
Cache cache = getCache(cacheId); if (_cache == null) {
if (cache != null) { return;
for (Object key : cache.getKeys()) { }
Serializable sKey = (Serializable) key; CacheAdapter.CacheBin<T> bin = _cache.getCache(cacheId);
Element element = cache.get(sKey); if (bin != null) {
for (Object key : bin.enumerateKeys()) {
CacheAdapter.CachedValue<T> element = bin.lookup((Serializable) key);
if (element != null) { if (element != null) {
@SuppressWarnings("unchecked") T value = (T) element.getValue(); filter.visitCacheEntry(this, cacheId, (Serializable) key, element.getValue());
filter.visitCacheEntry(this, cacheId, sKey, value);
} }
} }
} }
@@ -473,8 +483,7 @@ public class PersistenceContext
// outer exception; if I want a fucking stack trace, I'll call // outer exception; if I want a fucking stack trace, I'll call
// printStackTrace() thanksverymuch // printStackTrace() thanksverymuch
String msg = StringUtil.split(String.valueOf(sqe), "\n")[0]; String msg = StringUtil.split(String.valueOf(sqe), "\n")[0];
Log.info("Transient failure executing operation, " + log.info("Transient failure executing operation, retrying [error=" + msg + "].");
"retrying [error=" + msg + "].");
} else { } else {
String msg = isReadOnlyQuery ? "Query failure " + query String msg = isReadOnlyQuery ? "Query failure " + query
@@ -492,9 +501,11 @@ public class PersistenceContext
protected String _ident; protected String _ident;
protected ConnectionProvider _conprov; protected ConnectionProvider _conprov;
protected CacheManager _cachemgr;
protected DatabaseLiaison _liaison; 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 = protected Map<String, Set<CacheListener<?>>> _listenerSets =
new HashMap<String, Set<CacheListener<?>>>(); new HashMap<String, Set<CacheListener<?>>>();