Update of /cvsroot/hibernate/Hibernate3/src/org/hibernate/cache In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv7349/src/org/hibernate/cache Modified Files: CacheConcurrencyStrategy.java NonstrictReadWriteCache.java ReadOnlyCache.java ReadWriteCache.java TransactionalCache.java TreeCacheProvider.java Added Files: OptimisticCache.java OptimisticCacheSource.java OptimisticTreeCache.java OptimisticTreeCacheProvider.java Log Message: HHH-1457 : JBossCache 1.3.0 optimistic locking support --- NEW FILE: OptimisticCache.java --- package org.hibernate.cache; /** * A contract for transactional cache implementations which support * optimistic locking of items within the cache. * <p/> * The optimisitic locking capabilities are only utilized for * the entity cache regions. * <p/> * Unlike the methods on the {@link Cache} interface, all the methods * here will only ever be called from access scenarios where versioned * data is actually a possiblity (i.e., entity data). Be sure to consult * with {@link OptimisticCacheSource#isVersioned()} to determine whether * versioning is actually in effect. * * @author Steve Ebersole */ public interface OptimisticCache extends Cache { /** * Indicates the "source" of the cached data. Currently this will * only ever represent an {@link org.hibernate.persister.entity.EntityPersister}. * <p/> * Made available to the cache so that it can access certain information * about versioning strategy. * * @param source The source. */ public void setSource(OptimisticCacheSource source); /** * Called during {@link CacheConcurrencyStrategy#insert} processing for * transactional strategies. Indicates we have just performed an insert * into the DB and now need to cache that entity's data. * * @param key The cache key. * @param value The data to be cached. * @param currentVersion The entity's version; or null if not versioned. */ public void writeInsert(Object key, Object value, Object currentVersion); /** * Called during {@link CacheConcurrencyStrategy#update} processing for * transactional strategies. Indicates we have just performed an update * against the DB and now need to cache the updated state. * * @param key The cache key. * @param value The data to be cached. * @param currentVersion The entity's current version * @param previousVersion The entity's previous version (before the update); * or null if not versioned. */ public void writeUpdate(Object key, Object value, Object currentVersion, Object previousVersion); /** * Called during {@link CacheConcurrencyStrategy#put} processing for * transactional strategies. Indicates we have just loaded an entity's * state from the database and need it cached. * * @param key The cache key. * @param value The data to be cached. * @param currentVersion The entity's version; or null if not versioned. */ public void writeLoad(Object key, Object value, Object currentVersion); } --- NEW FILE: OptimisticCacheSource.java --- package org.hibernate.cache; import java.util.Comparator; /** * Contract for sources of optimistically lockable data sent to the second level * cache. * <p/> * Note currently {@link org.hibernate.persister.entity.EntityPersister}s are * the only viable source. * * @author Steve Ebersole */ public interface OptimisticCacheSource { /** * Does this source represent versioned (i.e., and thus optimistically * lockable) data? * * @return True if this source represents versioned data; false otherwise. */ public boolean isVersioned(); /** * Get the comparator used to compare two different version values together. * * @return An appropriate comparator. */ public Comparator getVersionComparator(); } --- NEW FILE: OptimisticTreeCache.java --- //$Id: OptimisticTreeCache.java,v 1.1 2006/02/09 20:48:42 steveebersole Exp $ package org.hibernate.cache; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Comparator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.cache.Fqn; import org.jboss.cache.optimistic.DataVersion; import org.jboss.cache.config.Option; import org.jboss.cache.lock.TimeoutException; /** * Represents a particular region within the given JBossCache TreeCache * utilizing TreeCache's optimistic locking capabilities. * * @see OptimisticTreeCacheProvider for more details * * @author Steve Ebersole */ public class OptimisticTreeCache implements OptimisticCache { // todo : eventually merge this with TreeCache and just add optional opt-lock support there. private static final Log log = LogFactory.getLog( OptimisticTreeCache.class); private static final String ITEM = "item"; private org.jboss.cache.TreeCache cache; private final String regionName; private final String userRegionName; private OptimisticCacheSource source; public OptimisticTreeCache(org.jboss.cache.TreeCache cache, String regionName) throws CacheException { this.cache = cache; userRegionName = regionName; this.regionName = regionName.replace('.', '/'); } // OptimisticCache impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public void setSource(OptimisticCacheSource source) { this.source = source; } public void writeInsert(Object key, Object value, Object currentVersion) { writeUpdate( key, value, currentVersion, null ); } public void writeUpdate(Object key, Object value, Object currentVersion, Object previousVersion) { try { Option option = null; if ( source != null ) { if ( source.isVersioned() ) { option = new Option(); option.setDataVersion( new DataVersionAdapter( currentVersion, previousVersion, source.getVersionComparator() ) ); } } cache.put( new Fqn( new Object[] { regionName, key } ), ITEM, value, option ); } catch (Exception e) { throw new CacheException(e); } } public void writeLoad(Object key, Object value, Object currentVersion) { try { Option option = new Option(); option.setFailSilently( true ); cache.remove( new Fqn( new Object[] { regionName, key } ), "ITEM", option ); if ( source != null ) { if ( source.isVersioned() ) { option.setDataVersion( new DataVersionAdapter( currentVersion, null, source.getVersionComparator() ) ); } } cache.put( new Fqn( new Object[] { regionName, key } ), ITEM, value, option ); } catch (Exception e) { throw new CacheException(e); } } // Cache impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public Object get(Object key) throws CacheException { try { Option option = new Option(); option.setFailSilently( true ); return cache.get( new Fqn( new Object[] { regionName, key } ), ITEM, option ); } catch (Exception e) { throw new CacheException(e); } } public Object read(Object key) throws CacheException { try { return cache.get( new Fqn( new Object[] { regionName, key } ), ITEM ); } catch (Exception e) { throw new CacheException(e); } } public void update(Object key, Object value) throws CacheException { try { cache.put( new Fqn( new Object[] { regionName, key } ), ITEM, value ); } catch (Exception e) { throw new CacheException(e); } } public void put(Object key, Object value) throws CacheException { try { // do the put outside the scope of the JTA txn Option option = new Option(); option.setFailSilently( true ); cache.put( new Fqn( new Object[] { regionName, key } ), ITEM, value, option ); } catch (TimeoutException te) { //ignore! log.debug("ignoring write lock acquisition failure"); } catch (Exception e) { throw new CacheException(e); } } public void remove(Object key) throws CacheException { try { cache.remove( new Fqn( new Object[] { regionName, key } ) ); } catch (Exception e) { throw new CacheException(e); } } public void clear() throws CacheException { try { cache.remove( new Fqn(regionName) ); } catch (Exception e) { throw new CacheException(e); } } public void destroy() throws CacheException { clear(); } public void lock(Object key) throws CacheException { throw new UnsupportedOperationException("TreeCache is a fully transactional cache" + regionName); } public void unlock(Object key) throws CacheException { throw new UnsupportedOperationException("TreeCache is a fully transactional cache: " + regionName); } public long nextTimestamp() { return System.currentTimeMillis() / 100; } public int getTimeout() { return 600; //60 seconds } public String getRegionName() { return userRegionName; } public long getSizeInMemory() { return -1; } public long getElementCountInMemory() { try { Set children = cache.getChildrenNames( new Fqn(regionName) ); return children == null ? 0 : children.size(); } catch (Exception e) { throw new CacheException(e); } } public long getElementCountOnDisk() { return 0; } public Map toMap() { try { Map result = new HashMap(); Set childrenNames = cache.getChildrenNames( new Fqn(regionName) ); if (childrenNames != null) { Iterator iter = childrenNames.iterator(); while ( iter.hasNext() ) { Object key = iter.next(); result.put( key, cache.get( new Fqn( new Object[] { regionName, key } ), ITEM ) ); } } return result; } catch (Exception e) { throw new CacheException(e); } } public String toString() { return "OptimisticTreeCache(" + userRegionName + ')'; } public static class DataVersionAdapter implements DataVersion { private final Object currentVersion; private final Object previousVersion; private final Comparator versionComparator; public DataVersionAdapter(Object currentVersion, Object previousVersion, Comparator versionComparator) { this.currentVersion = currentVersion; this.previousVersion = previousVersion; this.versionComparator = versionComparator; } public boolean newerThan(DataVersion dataVersion) { if ( previousVersion == null ) { log.warn( "Unexpected optimistic lock check on inserted data" ); } Object other = ( ( DataVersionAdapter ) dataVersion ).currentVersion; return versionComparator.compare( previousVersion, other ) > 1; } } } --- NEW FILE: OptimisticTreeCacheProvider.java --- //$Id: OptimisticTreeCacheProvider.java,v 1.1 2006/02/09 20:48:42 steveebersole Exp $ package org.hibernate.cache; import org.jboss.cache.PropertyConfigurator; import org.hibernate.transaction.TransactionManagerLookup; import org.hibernate.transaction.TransactionManagerLookupFactory; import org.hibernate.cfg.Environment; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.transaction.TransactionManager; import java.util.Properties; /** * Support for a standalone JBossCache TreeCache instance utilizing TreeCache's * optimistic locking capabilities. This capability was added in JBossCache * version 1.3.0; as such this provider will only work with that version or * higher. * <p/> * The TreeCache instance is configured via a local config resource. The * resource to be used for configuration can be controlled by specifying a value * for the {@link #CONFIG_RESOURCE} config property. * * @author Steve Ebersole */ public class OptimisticTreeCacheProvider implements CacheProvider { public static final String CONFIG_RESOURCE = "hibernate.cache.opt_tree_cache.config"; public static final String DEFAULT_CONFIG = "treecache.xml"; private static final String NODE_LOCKING_SCHEME = "OPTIMISTIC"; private static final Log log = LogFactory.getLog( OptimisticTreeCacheProvider.class ); private org.jboss.cache.TreeCache cache; /** * Construct and configure the Cache representation of a named cache region. * * @param regionName the name of the cache region * @param properties configuration settings * @return The Cache representation of the named cache region. * @throws CacheException * Indicates an error building the cache region. */ public Cache buildCache(String regionName, Properties properties) throws CacheException { return new OptimisticTreeCache( cache, regionName ); } public long nextTimestamp() { return System.currentTimeMillis() / 100; } /** * Prepare the underlying JBossCache TreeCache instance. * * @param properties All current config settings. * @throws CacheException * Indicates a problem preparing cache for use. */ public void start(Properties properties) { String resource = properties.getProperty( CONFIG_RESOURCE ); if ( resource == null ) { resource = DEFAULT_CONFIG; } log.debug( "Configuring TreeCache from resource [" + resource + "]" ); try { cache = new org.jboss.cache.TreeCache(); PropertyConfigurator config = new PropertyConfigurator(); config.configure( cache, resource ); TransactionManagerLookup transactionManagerLookup = TransactionManagerLookupFactory.getTransactionManagerLookup( properties ); if ( transactionManagerLookup == null ) { throw new CacheException( "JBossCache only supports optimisitc locking with a configured " + "TransactionManagerLookup (" + Environment.TRANSACTION_MANAGER_STRATEGY + ")" ); } cache.setTransactionManagerLookup( new TransactionManagerLookupAdaptor( transactionManagerLookup, properties ) ); if ( ! NODE_LOCKING_SCHEME.equalsIgnoreCase( cache.getNodeLockingScheme() ) ) { log.info( "Overriding node-locking-scheme to : " + NODE_LOCKING_SCHEME ); cache.setNodeLockingScheme( NODE_LOCKING_SCHEME ); } cache.start(); } catch ( Exception e ) { throw new CacheException( e ); } } public void stop() { if ( cache != null ) { cache.stop(); cache.destroy(); cache = null; } } public boolean isMinimalPutsEnabledByDefault() { return true; } static final class TransactionManagerLookupAdaptor implements org.jboss.cache.TransactionManagerLookup { private final TransactionManagerLookup tml; private final Properties props; TransactionManagerLookupAdaptor(TransactionManagerLookup tml, Properties props) { this.tml = tml; this.props = props; } public TransactionManager getTransactionManager() throws Exception { return tml.getTransactionManager( props ); } } public org.jboss.cache.TreeCache getUnderlyingCache() { return cache; } } Index: CacheConcurrencyStrategy.java =================================================================== RCS file: /cvsroot/hibernate/Hibernate3/src/org/hibernate/cache/CacheConcurrencyStrategy.java,v retrieving revision 1.7 retrieving revision 1.8 diff -u -d -r1.7 -r1.8 --- CacheConcurrencyStrategy.java 12 Feb 2005 07:19:08 -0000 1.7 +++ CacheConcurrencyStrategy.java 9 Feb 2006 20:48:42 -0000 1.8 @@ -6,37 +6,53 @@ /** * Implementors manage transactional access to cached data. Transactions * pass in a timestamp indicating transaction start time. Two different - * implementation patterns are provided for. A transaction-aware cache - * implementation might be wrapped by a "synchronous" concurrency strategy, - * where updates to the cache are written to the cache inside the transaction. - * A non transaction-aware cache would be wrapped by an "asynchronous" + * implementation patterns are provided for.<ul> + * <li>A transaction-aware cache implementation might be wrapped by a + * "synchronous" concurrency strategy, where updates to the cache are written + * to the cache inside the transaction.</li> + * <li>A non transaction-aware cache would be wrapped by an "asynchronous" * concurrency strategy, where items are merely "soft locked" during the * transaction and then updated during the "after transaction completion" - * phase. The soft lock is not an actual lock on the database row - - * only upon the cached representation of the item.<br> - * <br> - * For the client, update lifecycles are: lock->evict->release, - * lock->update->afterUpdate, insert->afterInsert.<br> - * <br> + * phase; the soft lock is not an actual lock on the database row - + * only upon the cached representation of the item.</li> + * </ul> + * <p/> + * In terms of entity caches, the expected call sequences are: <ul> + * <li><b>DELETES</b> : {@link #lock} -> {@link #evict} -> {@link #release}</li> + * <li><b>UPDATES</b> : {@link #lock} -> {@link #update} -> {@link #afterUpdate}</li> + * <li><b>INSERTS</b> : {@link #insert} -> {@link #afterInsert}</li> + * </ul> + * <p/> + * In terms of collection caches, all modification actions actually just + * invalidate the entry(s). The call sequence here is: + * {@link #lock} -> {@link #evict} -> {@link #release} + * <p/> * Note that, for an asynchronous cache, cache invalidation must be a two * step process (lock->release, or lock-afterUpdate), since this is the only - * way to guarantee consistency with the database for a nontransaction cache + * way to guarantee consistency with the database for a nontransactional cache * implementation. For a synchronous cache, cache invalidation is a single * step process (evict, or update). Hence, this interface defines a three * step process, to cater for both models. + * <p/> + * Note that query result caching does not go through a concurrency strategy; they + * are managed directly against the underlying {@link Cache cache regions}. */ public interface CacheConcurrencyStrategy { - + /** - * Attempt to retrieve an object from the cache. + * Attempt to retrieve an object from the cache. Mainly used in attempting + * to resolve entities/collections from the second level cache. + * * @param key * @param txTimestamp a timestamp prior to the transaction start time * @return the cached object or <tt>null</tt> * @throws CacheException */ public Object get(Object key, long txTimestamp) throws CacheException; + /** * Attempt to cache an object, after loading from the database. + * * @param key * @param value * @param txTimestamp a timestamp prior to the transaction start time @@ -55,37 +71,39 @@ boolean minimalPut) throws CacheException; - /** * We are going to attempt to update/delete the keyed object. This - * method is used by "asynchronous" concurrency strategies. The - * returned object must be passed back to release(), to release the + * method is used by "asynchronous" concurrency strategies. + * <p/> + * The returned object must be passed back to release(), to release the * lock. Concurrency strategies which do not support client-visible * locks may silently return null. + * * @param key * @param version * @throws CacheException */ public SoftLock lock(Object key, Object version) throws CacheException; - - + /** * Called after an item has become stale (before the transaction completes). * This method is used by "synchronous" concurrency strategies. */ public void evict(Object key) throws CacheException; + /** * Called after an item has been updated (before the transaction completes), * instead of calling evict(). * This method is used by "synchronous" concurrency strategies. */ - public boolean update(Object key, Object value) throws CacheException; + public boolean update(Object key, Object value, Object currentVersion, Object previousVersion) throws CacheException; + /** * Called after an item has been inserted (before the transaction completes), * instead of calling evict(). * This method is used by "synchronous" concurrency strategies. */ - public boolean insert(Object key, Object value) throws CacheException; + public boolean insert(Object key, Object value, Object currentVersion) throws CacheException; /** Index: NonstrictReadWriteCache.java =================================================================== RCS file: /cvsroot/hibernate/Hibernate3/src/org/hibernate/cache/NonstrictReadWriteCache.java,v retrieving revision 1.8 retrieving revision 1.9 diff -u -d -r1.8 -r1.9 --- NonstrictReadWriteCache.java 12 Feb 2005 07:19:08 -0000 1.8 +++ NonstrictReadWriteCache.java 9 Feb 2006 20:48:42 -0000 1.9 @@ -109,7 +109,7 @@ /** * Invalidate the item */ - public boolean update(Object key, Object value) throws CacheException { + public boolean insert(Object key, Object value, Object currentVersion) { evict(key); return false; } @@ -117,7 +117,7 @@ /** * Do nothing. */ - public boolean insert(Object key, Object value) throws CacheException { + public boolean update(Object key, Object value, Object currentVersion, Object previousVersion) { return false; } Index: ReadOnlyCache.java =================================================================== RCS file: /cvsroot/hibernate/Hibernate3/src/org/hibernate/cache/ReadOnlyCache.java,v retrieving revision 1.8 retrieving revision 1.9 diff -u -d -r1.8 -r1.9 --- ReadOnlyCache.java 16 Mar 2005 06:01:16 -0000 1.8 +++ ReadOnlyCache.java 9 Feb 2006 20:48:42 -0000 1.9 @@ -112,14 +112,14 @@ /** * Do nothing. */ - public boolean insert(Object key, Object value) throws CacheException { + public boolean insert(Object key, Object value, Object currentVersion) { return false; } /** * Unsupported! */ - public boolean update(Object key, Object value) throws CacheException { + public boolean update(Object key, Object value, Object currentVersion, Object previousVersion) { log.error("Application attempted to edit read only item: " + key); throw new UnsupportedOperationException("Can't write to a readonly object"); } Index: ReadWriteCache.java =================================================================== RCS file: /cvsroot/hibernate/Hibernate3/src/org/hibernate/cache/ReadWriteCache.java,v retrieving revision 1.9 retrieving revision 1.10 diff -u -d -r1.9 -r1.10 --- ReadWriteCache.java 30 Sep 2005 07:50:55 -0000 1.9 +++ ReadWriteCache.java 9 Feb 2006 20:48:42 -0000 1.10 @@ -311,14 +311,14 @@ /** * Do nothing. */ - public boolean insert(Object key, Object value) throws CacheException { + public boolean insert(Object key, Object value, Object currentVersion) { return false; } /** * Do nothing. */ - public boolean update(Object key, Object value) throws CacheException { + public boolean update(Object key, Object value, Object currentVersion, Object previousVersion) { return false; } Index: TransactionalCache.java =================================================================== RCS file: /cvsroot/hibernate/Hibernate3/src/org/hibernate/cache/TransactionalCache.java,v retrieving revision 1.10 retrieving revision 1.11 diff -u -d -r1.10 -r1.11 --- TransactionalCache.java 21 Apr 2005 07:57:19 -0000 1.10 +++ TransactionalCache.java 9 Feb 2006 20:48:42 -0000 1.11 @@ -47,7 +47,13 @@ return false; } if ( log.isDebugEnabled() ) log.debug("caching: " + key); - cache.put(key, value); +// cache.put(key, value); + if ( cache instanceof OptimisticCache ) { + ( ( OptimisticCache ) cache ).writeLoad( key, value, version ); + } + else { + cache.put( key, value ); + } return true; } @@ -66,15 +72,67 @@ //noop } + public boolean update(Object key, Object value, Object currentVersion, Object previousVersion) { + if ( log.isDebugEnabled() ) { + log.debug("updating: " + key); + } + if ( cache instanceof OptimisticCache ) { + ( ( OptimisticCache ) cache ).writeUpdate( key, value, currentVersion, previousVersion ); + } + else { + cache.update( key, value ); + } + return true; + } + + public boolean insert(Object key, Object value, Object currentVersion) throws CacheException { + if ( log.isDebugEnabled() ) { + log.debug("inserting: " + key); + } + if ( cache instanceof OptimisticCache ) { + ( ( OptimisticCache ) cache ).writeInsert( key, value, currentVersion ); + } + else { + cache.update( key, value ); + } + return true; + } + public boolean update(Object key, Object value) throws CacheException { - if ( log.isDebugEnabled() ) log.debug("updating: " + key); - cache.update(key, value); +// if ( log.isDebugEnabled() ) log.debug("updating: " + key); +// cache.update(key, value); +// return true; + if ( log.isDebugEnabled() ) { + log.debug("updating: " + key); + } + if ( cache instanceof OptimisticCache ) { + // todo : need to call writeUpdate() instead + // but that requires this method to take previous and current versions + // ( ( OptimisticCache ) cache ).writeUpdate( key, value, currentVersion, previousVersion ); + ( ( OptimisticCache ) cache ).update( key, value ); + } + else { + cache.update( key, value ); + } return true; } public boolean insert(Object key, Object value) throws CacheException { - if ( log.isDebugEnabled() ) log.debug("inserting: " + key); - cache.update(key, value); +// if ( log.isDebugEnabled() ) log.debug("inserting: " + key); +// cache.update(key, value); +// return true; + if ( log.isDebugEnabled() ) { + log.debug("inserting: " + key); + } + if ( cache instanceof OptimisticCache ) { + // todo : need to call writeInsert() instead + // but that requires this method to take current version + // ( ( OptimisticCache ) cache ).writeInsert( key, value, currentVersion ); + ( ( OptimisticCache ) cache ).update( key, value ); + } + else { + cache.update( key, value ); + } return true; } Index: TreeCacheProvider.java =================================================================== RCS file: /cvsroot/hibernate/Hibernate3/src/org/hibernate/cache/TreeCacheProvider.java,v retrieving revision 1.6 retrieving revision 1.7 diff -u -d -r1.6 -r1.7 --- TreeCacheProvider.java 16 Mar 2005 06:01:17 -0000 1.6 +++ TreeCacheProvider.java 9 Feb 2006 20:48:42 -0000 1.7 @@ -4,6 +4,8 @@ import org.jboss.cache.PropertyConfigurator; import org.hibernate.transaction.TransactionManagerLookup; import org.hibernate.transaction.TransactionManagerLookupFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import javax.transaction.TransactionManager; import java.util.Properties; @@ -16,6 +18,11 @@ */ public class TreeCacheProvider implements CacheProvider { + public static final String CONFIG_RESOURCE = "hibernate.cache.tree_cache.config"; + public static final String DEFAULT_CONFIG = "treecache.xml"; + + private static final Log log = LogFactory.getLog( TreeCacheProvider.class ); + private org.jboss.cache.TreeCache cache; private TransactionManager transactionManager; @@ -43,10 +50,15 @@ * @throws CacheException Indicates a problem preparing cache for use. */ public void start(Properties properties) { + String resource = properties.getProperty( CONFIG_RESOURCE ); + if ( resource == null ) { + resource = DEFAULT_CONFIG; + } + log.debug( "Configuring TreeCache from resource [" + resource + "]" ); try { cache = new org.jboss.cache.TreeCache(); PropertyConfigurator config = new PropertyConfigurator(); - config.configure(cache, "treecache.xml"); + config.configure( cache, resource ); TransactionManagerLookup transactionManagerLookup = TransactionManagerLookupFactory.getTransactionManagerLookup(properties); if (transactionManagerLookup!=null) { cache.setTransactionManagerLookup( new TransactionManagerLookupAdaptor(transactionManagerLookup, properties) ); @@ -83,4 +95,7 @@ } } + public org.jboss.cache.TreeCache getUnderlyingCache() { + return cache; + } } |