[hibernate-commits] Hibernate SVN: r17234 - in core/branches/INFINISPAN/cache-infinispan: src/main/java/org/hibernate/cache/infinispan and 10 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Wed Aug 5 14:31:51 EDT 2009


Author: galder.zamarreno at jboss.com
Date: 2009-08-05 14:31:51 -0400 (Wed, 05 Aug 2009)
New Revision: 17234

Added:
   core/branches/INFINISPAN/cache-infinispan/.settings/
   core/branches/INFINISPAN/cache-infinispan/src/main/resources/org/
   core/branches/INFINISPAN/cache-infinispan/src/main/resources/org/hibernate/
   core/branches/INFINISPAN/cache-infinispan/src/main/resources/org/hibernate/cache/
   core/branches/INFINISPAN/cache-infinispan/src/main/resources/org/hibernate/cache/infinispan/
   core/branches/INFINISPAN/cache-infinispan/src/main/resources/org/hibernate/cache/infinispan/builder/
   core/branches/INFINISPAN/cache-infinispan/src/main/resources/org/hibernate/cache/infinispan/builder/ispn4-configs.xml
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/AbstractDualNodeTestCase.java
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/ClusterAwareRegionFactory.java
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Contact.hbm.xml
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Contact.java
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Customer.java
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeConnectionProviderImpl.java
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeJtaTransactionImpl.java
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeJtaTransactionManagerImpl.java
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeTransactionManagerLookup.java
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/EntityCollectionInvalidationTestCase.java
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/util/
Removed:
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/cache/
Modified:
   core/branches/INFINISPAN/cache-infinispan/src/main/java/org/hibernate/cache/infinispan/InfinispanRegionFactory.java
   core/branches/INFINISPAN/cache-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseRegion.java
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/AbstractInfinispanTestCase.java
   core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/BasicTransactionalTestCase.java
Log:
[ISPN-6] Added more tests and sample infinispan configuiration files. More work still to come.

Modified: core/branches/INFINISPAN/cache-infinispan/src/main/java/org/hibernate/cache/infinispan/InfinispanRegionFactory.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/main/java/org/hibernate/cache/infinispan/InfinispanRegionFactory.java	2009-08-05 18:10:41 UTC (rev 17233)
+++ core/branches/INFINISPAN/cache-infinispan/src/main/java/org/hibernate/cache/infinispan/InfinispanRegionFactory.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -14,103 +14,123 @@
 import org.hibernate.cache.infinispan.query.InfinispanQueryResultsRegion;
 import org.hibernate.cache.infinispan.timestamp.InfinispanTimestampsRegion;
 import org.hibernate.cfg.Settings;
+import org.hibernate.util.PropertiesHelper;
 import org.infinispan.manager.CacheManager;
 import org.infinispan.manager.DefaultCacheManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * A {@link RegionFactory} for <a
- * href="http://www.jboss.org/infinispan">Infinispan</a>-backed cache regions.
+ * A {@link RegionFactory} for <a href="http://www.jboss.org/infinispan">Infinispan</a>-backed cache
+ * regions.
  * 
  * @author Chris Bredesen
  */
 public class InfinispanRegionFactory implements RegionFactory {
-	private static final Logger log = LoggerFactory.getLogger(InfinispanRegionFactory.class);
+   
+   private static final Logger log = LoggerFactory.getLogger(InfinispanRegionFactory.class);
+   
+   public static final String INFINISPAN_CONFIG_RESOURCE_PROP = "hibernate.cache.infinispan.cfg";
+   
+   public static final String DEF_INFINISPAN_CONFIG_RESOURCE = "org/hibernate/cache/infinispan/builder/ispn4-configs.xml";
+   
+   private CacheManager manager;
 
-	final CacheManager cacheManager = new DefaultCacheManager( false );
+   /**
+    * Create a new instance using the default configuration.
+    */
+   public InfinispanRegionFactory() {
+   }
 
-	/**
-	 * Create a new instance using the default configuration.
-	 */
-	public InfinispanRegionFactory() {
-	}
+   /**
+    * Create a new instance using conifguration properties in <code>props</code>.
+    * 
+    * @param props
+    *           Environmental properties; currently unused.
+    */
+   public InfinispanRegionFactory(Properties props) {
+   }
 
-	/**
-	 * Create a new instance using conifguration properties in
-	 * <code>props</code>.
-	 * 
-	 * @param props Environmental properties; currently unused.
-	 */
-	public InfinispanRegionFactory( Properties props ) {
-	}
+   /**
+    * {@inheritDoc}
+    */
+   public CollectionRegion buildCollectionRegion(String regionName, Properties properties,
+            CacheDataDescription metadata) throws CacheException {
+      log.debug("Building collection cache region [" + regionName + "]");
+      // TODO: Clarify whether for collections, we should just pick the entity name without the collection name from the CacheManager
+      return new InfinispanCollectionRegion(manager.getCache(regionName), regionName, metadata);
+   }
 
-	/**
-	 * {@inheritDoc}
-	 */
-	public CollectionRegion buildCollectionRegion( String regionName, Properties properties,
-			CacheDataDescription metadata ) throws CacheException {
-		log.debug( "Building collection cache region [" + regionName + "]" );
-		return new InfinispanCollectionRegion( cacheManager.getCache( regionName ), regionName,
-				metadata );
-	}
+   /**
+    * {@inheritDoc}
+    */
+   public EntityRegion buildEntityRegion(String regionName, Properties properties,
+            CacheDataDescription metadata) throws CacheException {
+      log.debug("Building entity cache region [" + regionName + "]");
+      return new InfinispanEntityRegion(manager.getCache(regionName), regionName, metadata);
+   }
 
-	/**
-	 * {@inheritDoc}
-	 */
-	public EntityRegion buildEntityRegion( String regionName, Properties properties,
-			CacheDataDescription metadata ) throws CacheException {
-		log.debug( "Building entity cache region [" + regionName + "]" );
-		return new InfinispanEntityRegion( cacheManager.getCache( regionName ), regionName,
-				metadata );
-	}
+   /**
+    * {@inheritDoc}
+    */
+   public QueryResultsRegion buildQueryResultsRegion(String regionName, Properties properties)
+            throws CacheException {
+      log.debug("Building query results cache region [" + regionName + "]");
+      return new InfinispanQueryResultsRegion(manager.getCache(regionName), regionName);
+   }
 
-	/**
-	 * {@inheritDoc}
-	 */
-	public QueryResultsRegion buildQueryResultsRegion( String regionName, Properties properties )
-			throws CacheException {
-		log.debug( "Building query results cache region [" + regionName + "]" );
-		return new InfinispanQueryResultsRegion( cacheManager.getCache(regionName), regionName);
-	}
+   /**
+    * {@inheritDoc}
+    */
+   public TimestampsRegion buildTimestampsRegion(String regionName, Properties properties)
+            throws CacheException {
+      log.debug("Building timestamps cache region [" + regionName + "]");
+      return new InfinispanTimestampsRegion(manager.getCache(regionName), regionName);
+   }
 
-	/**
-	 * {@inheritDoc}
-	 */
-	public TimestampsRegion buildTimestampsRegion( String regionName, Properties properties )
-			throws CacheException {
-		log.debug( "Building timestamps cache region [" + regionName + "]" );
-		return new InfinispanTimestampsRegion( cacheManager.getCache( regionName ), regionName );
-	}
+   /**
+    * {@inheritDoc}
+    */
+   public boolean isMinimalPutsEnabledByDefault() {
+      return false;
+   }
 
-	/**
-	 * {@inheritDoc}
-	 */
-	public boolean isMinimalPutsEnabledByDefault() {
-		return false;
-	}
+   /**
+    * {@inheritDoc}
+    */
+   public long nextTimestamp() {
+      return 0;
+   }
+   
+   public void setCacheManager(CacheManager manager) {
+      this.manager = manager;
+   }
 
-	/**
-	 * {@inheritDoc}
-	 */
-	public long nextTimestamp() {
-		return 0;
-	}
+   public CacheManager getCacheManager() {
+      return manager;
+   }
 
-	/**
-	 * {@inheritDoc}
-	 */
-	public void start( Settings settings, Properties properties ) throws CacheException {
-		log.debug( "Starting Infinispan CacheManager" );
-		this.cacheManager.start();
-	}
+   /**
+    * {@inheritDoc}
+    */
+   public void start(Settings settings, Properties properties) throws CacheException {
+      log.debug("Starting Infinispan CacheManager");
+      try {
+         String configLoc = PropertiesHelper.getString(INFINISPAN_CONFIG_RESOURCE_PROP, properties, DEF_INFINISPAN_CONFIG_RESOURCE);
+         manager = new DefaultCacheManager(configLoc);
+      } catch (CacheException ce) {
+         throw ce;
+      } catch (Throwable t) {
+          throw new CacheException("Unable to start region factory", t);
+      }
+   }
 
-	/**
-	 * {@inheritDoc}
-	 */
-	public void stop() {
-		log.debug( "Stopping Infinispan CacheManager" );
-		this.cacheManager.stop();
-	}
+   /**
+    * {@inheritDoc}
+    */
+   public void stop() {
+      log.debug("Stopping Infinispan CacheManager");
+      manager.stop();
+   }
 
 }
\ No newline at end of file

Modified: core/branches/INFINISPAN/cache-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseRegion.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseRegion.java	2009-08-05 18:10:41 UTC (rev 17233)
+++ core/branches/INFINISPAN/cache-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseRegion.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -1,6 +1,5 @@
 package org.hibernate.cache.infinispan.impl;
 
-import java.util.Collections;
 import java.util.Map;
 
 import org.hibernate.cache.CacheException;
@@ -8,69 +7,69 @@
 import org.infinispan.Cache;
 
 /**
- * Support for Infinispan {@link Region}s. Handles common "utility" methods for
- * an underlying named Cache. In other words, this implementation doesn't
- * actually read or write data. Subclasses are expected to provide core cache
- * interaction appropriate to the semantics needed.
+ * Support for Infinispan {@link Region}s. Handles common "utility" methods for an underlying named
+ * Cache. In other words, this implementation doesn't actually read or write data. Subclasses are
+ * expected to provide core cache interaction appropriate to the semantics needed.
  * 
  * @author Chris Bredesen
+ * @author Galder Zamarreño
  */
 abstract class BaseRegion implements Region {
-	private final Cache<Object, Object> cache;
-	private final String name;
+   private final Cache<Object, Object> cache;
+   private final String name;
 
-	public BaseRegion( Cache<Object, Object> cache, String name ) {
-		this.cache = cache;
-		this.name = name;
-	}
-	
-	public Cache<Object, Object> getCache() {
-		return cache;
-	}
+   public BaseRegion(Cache<Object, Object> cache, String name) {
+      this.cache = cache;
+      this.name = name;
+   }
 
-	public String getName() {
-		return name;
-	}
+   public Cache<Object, Object> getCache() {
+      return cache;
+   }
 
-	public long getElementCountInMemory() {
-		return cache.size();
-	}
+   public String getName() {
+      return name;
+   }
 
-	/**
-	 * Not supported.
-	 * 
-	 * @return -1
-	 */
-	public long getElementCountOnDisk() {
-		return -1;
-	}
+   public long getElementCountInMemory() {
+      return cache.size();
+   }
 
-	/**
-	 * Not supported.
-	 * 
-	 * @return -1
-	 */
-	public long getSizeInMemory() {
-		return -1;
-	}
+   /**
+    * Not supported.
+    * 
+    * @return -1
+    */
+   public long getElementCountOnDisk() {
+      return -1;
+   }
 
-	public int getTimeout() {
-		// TODO Auto-generated method stub
-		return 0;
-	}
+   /**
+    * Not supported.
+    * 
+    * @return -1
+    */
+   public long getSizeInMemory() {
+      return -1;
+   }
 
-	public long nextTimestamp() {
-		// TODO Auto-generated method stub
-		return 0;
-	}
+   public int getTimeout() {
+      // TODO Auto-generated method stub
+      return 0;
+   }
 
-	@SuppressWarnings("unchecked")
-	public Map toMap() {
-		return Collections.EMPTY_MAP;
-	}
+   public long nextTimestamp() {
+      // TODO Auto-generated method stub
+      return 0;
+   }
 
-	public void destroy() throws CacheException {
-		// TODO see if we need to do this even in spite of RF.shutdown()
-	}
+   @SuppressWarnings("unchecked")
+   public Map toMap() {
+      return cache;
+   }
 
+   public void destroy() throws CacheException {
+      // TODO see if we need to do this even in spite of RF.shutdown()
+   }
+
 }
\ No newline at end of file

Added: core/branches/INFINISPAN/cache-infinispan/src/main/resources/org/hibernate/cache/infinispan/builder/ispn4-configs.xml
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/main/resources/org/hibernate/cache/infinispan/builder/ispn4-configs.xml	                        (rev 0)
+++ core/branches/INFINISPAN/cache-infinispan/src/main/resources/org/hibernate/cache/infinispan/builder/ispn4-configs.xml	2009-08-05 18:31:51 UTC (rev 17234)
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<infinispan xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:infinispan:config:4.0">
+   <global>
+      <transport transportClass = "org.infinispan.remoting.transport.jgroups.JGroupsTransport" 
+            clusterName="infinispan-hibernate-cluster" distributedSyncTimeout="50000">
+         <!-- Note that the JGroups transport uses sensible defaults if no configuration property is defined. -->
+         <!-- TODO: Change to udp.xml once streaming transfer requirement has been removed.  -->
+         <property name="configurationFile" value="flush-udp.xml"/>
+         <!-- See the JGroupsTransport javadocs for more flags -->
+      </transport>
+      <serialization marshallerClass="org.infinispan.marshall.VersionAwareMarshaller" version="4.0"/>
+   </global>
+
+   <!-- Default configuration is appropriate for entity/collection caching. -->
+   <default>
+      <clustering mode="invalidation">
+         <stateRetrieval fetchInMemoryState="false" timeout="20000"/>
+         <sync replTimeout="20000"/>
+      </clustering>
+      <!-- Note: REPEATABLE_READ is only useful if the application evicts/clears entities 
+        from the Hibernate Session and then expects to repeatably re-read them in 
+        the same transaction. Otherwise, the Session's internal cache provides a 
+        repeatable-read semantic. Before choosing this config, carefully read the docs
+        and make sure you really need REPEATABLE_READ.
+       -->
+      <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000"/>      
+      <!--
+         Eviction configuration.  WakeupInterval defines how often the eviction thread runs, in milliseconds.  0 means
+         the eviction thread will never run.  A separate executor is used for eviction in each cache.
+      -->
+      <eviction wakeUpInterval="5000" maxEntries="10000" strategy="FIFO"/>
+      <expiration maxIdle="100000"/>
+      <lazyDeserialization enabled="true"/>      
+   </default>
+
+   <!-- A config appropriate for query caching. Does not replicate
+        queries. DO NOT STORE TIMESTAMPS IN THIS CACHE.
+   -->
+   <namedCache name="org.hibernate.cache.StandardQueryCache">
+      <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000"/>      
+      <!--
+         Eviction configuration.  WakeupInterval defines how often the eviction thread runs, in milliseconds.  0 means
+         the eviction thread will never run.  A separate executor is used for eviction in each cache.
+      -->
+      <eviction wakeUpInterval="5000" maxEntries="10000" strategy="FIFO"/>
+      <expiration maxIdle="100000"/>
+   </namedCache>
+
+   <!-- Optimized for timestamp caching. A clustered timestamp cache
+        is required if query caching is used, even if the query cache
+        itself is configured with CacheMode=LOCAL.
+   -->
+   <namedCache name="org.hibernate.cache.UpdateTimestampsCache">
+      <clustering mode="REPL">
+         <stateRetrieval fetchInMemoryState="true" timeout="20000"/>
+         <async/>
+      </clustering>
+      <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000"/>      
+      <!--  Don't ever evict modification timestamps -->
+      <lazyDeserialization enabled="true"/>
+   </namedCache>
+
+</infinispan>
\ No newline at end of file

Modified: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/AbstractInfinispanTestCase.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/AbstractInfinispanTestCase.java	2009-08-05 18:10:41 UTC (rev 17233)
+++ core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/AbstractInfinispanTestCase.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -1,94 +1,104 @@
 package org.hibernate.test.cache.infinispan.functional;
 
+import java.util.Map;
+
 import org.hibernate.Session;
 import org.hibernate.junit.functional.FunctionalTestCase;
 import org.hibernate.stat.SecondLevelCacheStatistics;
 import org.hibernate.stat.Statistics;
 
 public abstract class AbstractInfinispanTestCase extends FunctionalTestCase {
-	private final String cacheConcurrencyStrategy;
-	
-	public AbstractInfinispanTestCase( String string, String cacheConcurrencyStrategy ) {
-		super( string );
-		this.cacheConcurrencyStrategy = cacheConcurrencyStrategy;
-	}
-	
-	public String[] getMappings() {
-		return new String[] { "cache/infinispan/functional/Item.hbm.xml" };
-	}
-	
-	@Override
-	public String getCacheConcurrencyStrategy() {
-		return cacheConcurrencyStrategy;
-	}
+   private final String cacheConcurrencyStrategy;
 
-	public void testEntityCache() {
-		Item item = new Item("chris", "Chris's Item");
-		
-		Session s = openSession();
-		Statistics stats = s.getSessionFactory().getStatistics();
-		s.getTransaction().begin();
-		s.persist( item );
-		s.getTransaction().commit();
-		s.close();
-		
-		s = openSession();
-		Item found = (Item) s.load( Item.class, item.getId() );
-		System.out.println( stats );
-		assertEquals( item.getDescription(), found.getDescription() );
-		assertEquals( 0, stats.getSecondLevelCacheMissCount() );
-		assertEquals( 1, stats.getSecondLevelCacheHitCount() );
-		s.delete( found );
-		s.close();
-	}
-	
-	public void testQueryCache() {
-		Item item = new Item( "chris", "Chris's Item" );
-		
-		Session s = openSession();
-		s.getTransaction().begin();
-		s.persist( item );
-		s.getTransaction().commit();
-		s.close();
+   public AbstractInfinispanTestCase(String string, String cacheConcurrencyStrategy) {
+      super(string);
+      this.cacheConcurrencyStrategy = cacheConcurrencyStrategy;
+   }
 
-		s = openSession();
-		s.createQuery( "from Item" ).setCacheable( true ).list();
-		s.close();
+   public String[] getMappings() {
+      return new String[] { "cache/infinispan/functional/Item.hbm.xml" };
+   }
 
-		s = openSession();
-		Statistics stats = s.getSessionFactory().getStatistics();
-		s.createQuery( "from Item" ).setCacheable( true ).list();
-		assertEquals( 1, stats.getQueryCacheHitCount() );
-		s.createQuery( "delete from Item" ).executeUpdate();
-		s.close();
-	}
-	
-	public void testCollectionCache() {
-		Item item = new Item( "chris", "Chris's Item" );
-		Item another = new Item( "another", "Owned Item" );
-		item.addItem( another );
+   @Override
+   public String getCacheConcurrencyStrategy() {
+      return cacheConcurrencyStrategy;
+   }
 
-		Session s = openSession();
-		s.getTransaction().begin();
-		s.persist( item );
-		s.persist( another );
-		s.getTransaction().commit();
-		s.close();
+   public void testEntityCache() {
+      Item item = new Item("chris", "Chris's Item");
 
-		s = openSession();
-		Statistics stats = s.getSessionFactory().getStatistics();
-		Item loaded = (Item) s.load( Item.class, item.getId() );
-		assertEquals( 1, loaded.getItems().size() );
-		s.close();
+      Session s = openSession();
+      Statistics stats = s.getSessionFactory().getStatistics();
+      s.getTransaction().begin();
+      s.persist(item);
+      s.getTransaction().commit();
+      s.close();
 
-		s = openSession();
-		SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" );
-		Item loadedWithCachedCollection = (Item) s.load( Item.class, item.getId() );
-		stats.logSummary();
-		assertEquals( item.getName(), loadedWithCachedCollection.getName() );
-		assertEquals( item.getItems().size(), loadedWithCachedCollection.getItems().size() );
-		assertEquals( 1, cStats.getHitCount() );
-		s.close();
-	}
+      s = openSession();
+      Item found = (Item) s.load(Item.class, item.getId());
+      System.out.println(stats);
+      assertEquals(item.getDescription(), found.getDescription());
+      assertEquals(0, stats.getSecondLevelCacheMissCount());
+      assertEquals(1, stats.getSecondLevelCacheHitCount());
+      s.delete(found);
+      s.close();
+   }
 
+   public void testQueryCache() {
+      Item item = new Item("chris", "Chris's Item");
+
+      Session s = openSession();
+      s.getTransaction().begin();
+      s.persist(item);
+      s.getTransaction().commit();
+      s.close();
+
+      s = openSession();
+      s.createQuery("from Item").setCacheable(true).list();
+      s.close();
+
+      s = openSession();
+      Statistics stats = s.getSessionFactory().getStatistics();
+      s.createQuery("from Item").setCacheable(true).list();
+      assertEquals(1, stats.getQueryCacheHitCount());
+      s.createQuery("delete from Item").executeUpdate();
+      s.close();
+   }
+   
+   public void testCollectionCache() {
+      Item item = new Item("chris", "Chris's Item");
+      Item another = new Item("another", "Owned Item");
+      item.addItem(another);
+
+      Session s = openSession();
+      s.getTransaction().begin();
+      s.persist(item);
+      s.persist(another);
+      s.getTransaction().commit();
+      s.close();
+
+      s = openSession();
+      Statistics stats = s.getSessionFactory().getStatistics();
+      Item loaded = (Item) s.load(Item.class, item.getId());
+      assertEquals(1, loaded.getItems().size());
+      s.close();
+
+      s = openSession();
+      SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics(Item.class.getName() + ".items");
+      Item loadedWithCachedCollection = (Item) s.load(Item.class, item.getId());
+      stats.logSummary();
+      assertEquals(item.getName(), loadedWithCachedCollection.getName());
+      assertEquals(item.getItems().size(), loadedWithCachedCollection.getItems().size());
+      assertEquals(1, cStats.getHitCount());
+      s.close();
+   }
+   
+   public void testEmptySecondLevelCacheEntry() throws Exception {
+      getSessions().evictEntity(Item.class.getName());
+      Statistics stats = getSessions().getStatistics();
+      stats.clear();
+      SecondLevelCacheStatistics statistics = stats.getSecondLevelCacheStatistics(Item.class.getName() + ".items");
+      Map cacheEntries = statistics.getEntries();
+      assertEquals(0, cacheEntries.size());
+  }
 }
\ No newline at end of file

Modified: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/BasicTransactionalTestCase.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/BasicTransactionalTestCase.java	2009-08-05 18:10:41 UTC (rev 17233)
+++ core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/BasicTransactionalTestCase.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -1,9 +1,114 @@
 package org.hibernate.test.cache.infinispan.functional;
 
+import java.io.Serializable;
+
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+import org.hibernate.cache.entry.CacheEntry;
+import org.hibernate.stat.SecondLevelCacheStatistics;
+
 public class BasicTransactionalTestCase extends AbstractInfinispanTestCase {
 
-	public BasicTransactionalTestCase( String string ) {
-		super( string, "transactional" );
-	}
-	
+   public BasicTransactionalTestCase(String string) {
+      super(string, "transactional");
+   }
+
+   public void testStaleWritesLeaveCacheConsistent() {
+      Session s = openSession();
+      Transaction txn = s.beginTransaction();
+      VersionedItem item = new VersionedItem();
+      item.setName("steve");
+      item.setDescription("steve's item");
+      s.save(item);
+      txn.commit();
+      s.close();
+
+      Long initialVersion = item.getVersion();
+
+      // manually revert the version property
+      item.setVersion(new Long(item.getVersion().longValue() - 1));
+
+      try {
+          s = openSession();
+          txn = s.beginTransaction();
+          s.update(item);
+          txn.commit();
+          s.close();
+          fail("expected stale write to fail");
+      } catch (Throwable expected) {
+          // expected behavior here
+          if (txn != null) {
+              try {
+                  txn.rollback();
+              } catch (Throwable ignore) {
+              }
+          }
+      } finally {
+          if (s != null && s.isOpen()) {
+              try {
+                  s.close();
+              } catch (Throwable ignore) {
+              }
+          }
+      }
+
+      // check the version value in the cache...
+      SecondLevelCacheStatistics slcs = sfi().getStatistics().getSecondLevelCacheStatistics(VersionedItem.class.getName());
+
+      Object entry = slcs.getEntries().get(item.getId());
+      Long cachedVersionValue;
+      cachedVersionValue = (Long) ((CacheEntry) entry).getVersion();
+      assertEquals(initialVersion.longValue(), cachedVersionValue.longValue());
+
+      // cleanup
+      s = openSession();
+      txn = s.beginTransaction();
+      item = (VersionedItem) s.load(VersionedItem.class, item.getId());
+      s.delete(item);
+      txn.commit();
+      s.close();
+  }
+
+   public void testQueryCacheInvalidation() {
+      Session s = openSession();
+      Transaction t = s.beginTransaction();
+      Item i = new Item();
+      i.setName("widget");
+      i.setDescription("A really top-quality, full-featured widget.");
+      s.persist(i);
+      t.commit();
+      s.close();
+
+      SecondLevelCacheStatistics slcs = s.getSessionFactory().getStatistics().getSecondLevelCacheStatistics(Item.class.getName());
+
+      assertEquals(slcs.getPutCount(), 1);
+      assertEquals(slcs.getElementCountInMemory(), 1);
+      assertEquals(slcs.getEntries().size(), 1);
+
+      s = openSession();
+      t = s.beginTransaction();
+      i = (Item) s.get(Item.class, i.getId());
+
+      assertEquals(slcs.getHitCount(), 1);
+      assertEquals(slcs.getMissCount(), 0);
+
+      i.setDescription("A bog standard item");
+
+      t.commit();
+      s.close();
+
+      assertEquals(slcs.getPutCount(), 2);
+
+      CacheEntry entry = (CacheEntry) slcs.getEntries().get(i.getId());
+      Serializable[] ser = entry.getDisassembledState();
+      assertTrue(ser[0].equals("widget"));
+      assertTrue(ser[1].equals("A bog standard item"));
+      
+      // cleanup
+      s = openSession();
+      t = s.beginTransaction();
+      s.delete(i);
+      t.commit();
+      s.close();
+   }
 }

Added: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/AbstractDualNodeTestCase.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/AbstractDualNodeTestCase.java	                        (rev 0)
+++ core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/AbstractDualNodeTestCase.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -0,0 +1,236 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This 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 software 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.hibernate.test.cache.infinispan.functional.cluster;
+
+import org.hibernate.Session;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.cfg.Environment;
+import org.hibernate.cfg.Mappings;
+import org.hibernate.dialect.Dialect;
+import org.hibernate.engine.SessionFactoryImplementor;
+import org.hibernate.junit.functional.ExecutionEnvironment;
+import org.hibernate.junit.functional.FunctionalTestCase;
+import org.hibernate.transaction.CMTTransactionFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * AbstractDualNodeTestCase.
+ * 
+ * @author Galder Zamarreño
+ */
+public class AbstractDualNodeTestCase extends FunctionalTestCase {
+   private static final Logger log = LoggerFactory.getLogger(AbstractDualNodeTestCase.class);
+   public static final String NODE_ID_PROP = "hibernate.test.cluster.node.id";
+   public static final String LOCAL = "local";
+   public static final String REMOTE = "remote";
+   private ExecutionEnvironment secondNodeEnvironment;
+   private Session secondNodeSession;
+
+   public AbstractDualNodeTestCase(String string) {
+      super(string);
+   }
+   
+   public String[] getMappings() {
+      return new String[] { "cache/infinispan/functional/cluster/Contact.hbm.xml" };
+   }
+   
+   @Override
+   public String getCacheConcurrencyStrategy() {
+      return "transactional";
+   }
+   
+   protected Class getCacheRegionFactory() {
+      return ClusterAwareRegionFactory.class;
+   }
+
+   @Override
+   public void configure(Configuration cfg) {
+      standardConfigure(cfg);
+      configureFirstNode(cfg);
+   }
+
+   @Override
+   protected void prepareTest() throws Exception {
+      log.info("Building second node locally managed execution env");
+      secondNodeEnvironment = new ExecutionEnvironment(new SecondNodeSettings());
+      secondNodeEnvironment.initialize();
+      super.prepareTest();
+   }
+   
+   @Override
+   protected void runTest() throws Throwable {
+      try {
+          super.runTest();
+      } finally {
+         if ( secondNodeSession != null && secondNodeSession.isOpen() ) {
+             if ( secondNodeSession.isConnected() ) {
+                secondNodeSession.connection().rollback();
+             }
+             secondNodeSession.close();
+             secondNodeSession = null;
+             fail( "unclosed session" );
+         } else {
+            secondNodeSession = null;
+         }
+         
+      }
+   }
+
+   @Override
+   protected void cleanupTest() throws Exception {
+      try {
+          super.cleanupTest();
+      
+          log.info( "Destroying second node locally managed execution env" );
+          secondNodeEnvironment.complete();
+          secondNodeEnvironment = null;
+      } finally {
+         cleanupTransactionManagement();
+      }
+   }
+   
+   protected void cleanupTransactionManagement() {
+      DualNodeJtaTransactionManagerImpl.cleanupTransactions();
+      DualNodeJtaTransactionManagerImpl.cleanupTransactionManagers();
+   }
+
+   public ExecutionEnvironment getSecondNodeEnvironment() {
+      return secondNodeEnvironment;
+   }
+
+   protected Class getConnectionProviderClass() {
+      return DualNodeConnectionProviderImpl.class;
+   }
+
+   protected Class getTransactionManagerLookupClass() {
+      return DualNodeTransactionManagerLookup.class;
+   }
+
+   protected Class getTransactionFactoryClass() {
+      return CMTTransactionFactory.class;
+   }
+
+   /**
+    * Apply any node-specific configurations to our first node.
+    * 
+    * @param the
+    *           Configuration to update.
+    */
+   protected void configureFirstNode(Configuration cfg) {
+      cfg.setProperty(NODE_ID_PROP, LOCAL);
+   }
+
+   /**
+    * Apply any node-specific configurations to our second node.
+    * 
+    * @param the
+    *           Configuration to update.
+    */
+   protected void configureSecondNode(Configuration cfg) {
+      cfg.setProperty(NODE_ID_PROP, REMOTE);
+   }
+   
+   protected void sleep(long ms) {
+      try {
+          Thread.sleep(ms);
+      }
+      catch (InterruptedException e) {
+          log.warn("Interrupted during sleep", e);
+      }
+  }
+
+   private void standardConfigure(Configuration cfg) {
+      super.configure(cfg);
+
+      cfg.setProperty(Environment.CONNECTION_PROVIDER, getConnectionProviderClass().getName());
+      cfg.setProperty(Environment.TRANSACTION_MANAGER_STRATEGY, getTransactionManagerLookupClass().getName());
+      cfg.setProperty(Environment.TRANSACTION_STRATEGY, getTransactionFactoryClass().getName());
+      cfg.setProperty(Environment.CACHE_REGION_FACTORY, getCacheRegionFactory().getName());
+   }
+
+   /**
+    * Settings impl that delegates most calls to the DualNodeTestCase itself, but overrides the
+    * configure method to allow separate cache settings for the second node.
+    */
+   public class SecondNodeSettings implements ExecutionEnvironment.Settings {
+      private final AbstractDualNodeTestCase delegate;
+
+      public SecondNodeSettings() {
+         this.delegate = AbstractDualNodeTestCase.this;
+      }
+
+      /**
+       * This is the important one -- we extend the delegate's work by adding second-node specific
+       * settings
+       */
+      public void configure(Configuration arg0) {
+         delegate.standardConfigure(arg0);
+         configureSecondNode(arg0);
+      }
+
+      /**
+       * Disable creating of schemas; we let the primary session factory do that to our shared
+       * database.
+       */
+      public boolean createSchema() {
+         return false;
+      }
+
+      /**
+       * Disable creating of schemas; we let the primary session factory do that to our shared
+       * database.
+       */
+      public boolean recreateSchemaAfterFailure() {
+         return false;
+      }
+
+      public void afterConfigurationBuilt(Mappings arg0, Dialect arg1) {
+         delegate.afterConfigurationBuilt(arg0, arg1);
+      }
+
+      public void afterSessionFactoryBuilt(SessionFactoryImplementor arg0) {
+         delegate.afterSessionFactoryBuilt(arg0);
+      }
+
+      public boolean appliesTo(Dialect arg0) {
+         return delegate.appliesTo(arg0);
+      }
+
+      public String getBaseForMappings() {
+         return delegate.getBaseForMappings();
+      }
+
+      public String getCacheConcurrencyStrategy() {
+         return delegate.getCacheConcurrencyStrategy();
+      }
+
+      public String[] getMappings() {
+         return delegate.getMappings();
+      }
+
+      public boolean overrideCacheStrategy() {
+         return delegate.overrideCacheStrategy();
+      }
+   }
+
+}

Added: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/ClusterAwareRegionFactory.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/ClusterAwareRegionFactory.java	                        (rev 0)
+++ core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/ClusterAwareRegionFactory.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -0,0 +1,122 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This 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 software 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.hibernate.test.cache.infinispan.functional.cluster;
+
+import java.util.Hashtable;
+import java.util.Properties;
+
+import org.hibernate.cache.CacheDataDescription;
+import org.hibernate.cache.CacheException;
+import org.hibernate.cache.CollectionRegion;
+import org.hibernate.cache.EntityRegion;
+import org.hibernate.cache.QueryResultsRegion;
+import org.hibernate.cache.RegionFactory;
+import org.hibernate.cache.TimestampsRegion;
+import org.hibernate.cache.infinispan.InfinispanRegionFactory;
+import org.hibernate.cfg.Settings;
+import org.infinispan.manager.CacheManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ClusterAwareRegionFactory.
+ * 
+ * @author Galder Zamarreño
+ */
+public class ClusterAwareRegionFactory implements RegionFactory {
+   
+   private static final Logger log = LoggerFactory.getLogger(ClusterAwareRegionFactory.class);
+   private static final Hashtable<String, CacheManager> cacheManagers = new Hashtable<String, CacheManager>();
+
+   private final InfinispanRegionFactory delegate = new InfinispanRegionFactory();
+   private String cacheManagerName;
+   private boolean locallyAdded;
+   
+   public ClusterAwareRegionFactory(Properties props) {
+   }
+   
+   public static CacheManager getCacheManager(String name) {
+      return cacheManagers.get(name);
+   }
+   
+   public static void addCacheManager(String name, CacheManager manager) {
+      cacheManagers.put(name, manager);
+   }
+   
+   public static void clearCacheManagers() {
+      for (CacheManager manager : cacheManagers.values()) {
+         try {
+            manager.stop();
+         } catch (Exception e) {
+            log.error("Exception cleaning up CacheManager " + manager, e);
+         }
+      }
+      cacheManagers.clear();      
+   }
+
+   public void start(Settings settings, Properties properties) throws CacheException {
+      cacheManagerName = properties.getProperty(AbstractDualNodeTestCase.NODE_ID_PROP);
+      
+      CacheManager existing = getCacheManager(cacheManagerName);
+      locallyAdded = (existing == null);
+      
+      if (locallyAdded) {
+         delegate.start(settings, properties);
+         cacheManagers.put(cacheManagerName, delegate.getCacheManager());
+      } else {
+         delegate.setCacheManager(existing);
+      }      
+   }
+
+   public void stop() {
+      if (locallyAdded) cacheManagers.remove(cacheManagerName);     
+      delegate.stop();
+   }
+
+   public CollectionRegion buildCollectionRegion(String regionName, Properties properties,
+            CacheDataDescription metadata) throws CacheException {
+      return delegate.buildCollectionRegion(regionName, properties, metadata);
+   }
+
+   public EntityRegion buildEntityRegion(String regionName, Properties properties,
+            CacheDataDescription metadata) throws CacheException {
+      return delegate.buildEntityRegion(regionName, properties, metadata);
+   }
+
+   public QueryResultsRegion buildQueryResultsRegion(String regionName, Properties properties)
+            throws CacheException {
+      return delegate.buildQueryResultsRegion(regionName, properties);
+   }
+
+   public TimestampsRegion buildTimestampsRegion(String regionName, Properties properties)
+            throws CacheException {
+      return delegate.buildTimestampsRegion(regionName, properties);
+   }
+
+   public boolean isMinimalPutsEnabledByDefault() {
+      return delegate.isMinimalPutsEnabledByDefault();
+   }
+
+   public long nextTimestamp() {
+      return delegate.nextTimestamp();
+   }
+}

Added: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Contact.hbm.xml
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Contact.hbm.xml	                        (rev 0)
+++ core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Contact.hbm.xml	2009-08-05 18:31:51 UTC (rev 17234)
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC 
+	"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+	"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+<!--
+  ~ Hibernate, Relational Persistence for Idiomatic Java
+  ~
+  ~ Copyright (c) 2007, Red Hat Middleware LLC or third-party contributors as
+  ~ indicated by the @author tags or express copyright attribution
+  ~ statements applied by the authors.  All third-party contributions are
+  ~ distributed under license by Red Hat Middleware LLC.
+  ~
+  ~ This copyrighted material is made available to anyone wishing to use, modify,
+  ~ copy, or redistribute it subject to the terms and conditions of the GNU
+  ~ Lesser General Public License, as published by the Free Software Foundation.
+  ~
+  ~ This program 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 distribution; if not, write to:
+  ~ Free Software Foundation, Inc.
+  ~ 51 Franklin Street, Fifth Floor
+  ~ Boston, MA  02110-1301  USA
+  -->
+<hibernate-mapping
+	package="org.hibernate.test.cache.infinispan.functional.cluster">
+
+	<class name="Contact" table="Contacts">
+		<id name="id">
+			<generator class="increment"/>
+		</id>
+		<property name="name" not-null="true"/>
+		<property name="tlf" not-null="true"/>
+		<many-to-one name="customer" column="cust_id" class="Customer" />
+	</class>
+
+	<class name="Customer" table="Customers">
+		<id name="id">
+			<generator class="increment"/>
+		</id>
+      <property name="name" not-null="true"/>
+      <set name="contacts" inverse="true" cascade = "save-update">
+         <key column="cust_id" />
+         <one-to-many class="Contact"/>
+      </set>
+      
+	</class>
+
+</hibernate-mapping>
\ No newline at end of file


Property changes on: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Contact.hbm.xml
___________________________________________________________________
Name: svn:executable
   + *

Added: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Contact.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Contact.java	                        (rev 0)
+++ core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Contact.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -0,0 +1,90 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors.  All third-party contributions are
+ * distributed under license by Red Hat Middleware LLC.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program 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 distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA  02110-1301  USA
+ */
+package org.hibernate.test.cache.infinispan.functional.cluster;
+
+import java.io.Serializable;
+
+/**
+ * Entity that has a many-to-one relationship to a Customer
+ */
+public class Contact implements Serializable {
+   Integer id;
+   String name;
+   String tlf;
+   Customer customer;
+
+   public Integer getId() {
+      return id;
+   }
+
+   public void setId(Integer id) {
+      this.id = id;
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public void setName(String name) {
+      this.name = name;
+   }
+
+   public String getTlf() {
+      return tlf;
+   }
+
+   public void setTlf(String tlf) {
+      this.tlf = tlf;
+   }
+
+   public Customer getCustomer() {
+      return customer;
+   }
+
+   public void setCustomer(Customer customer) {
+      this.customer = customer;
+   }
+
+   @Override
+   public boolean equals(Object o) {
+      if (o == this) return true;
+      if (!(o instanceof Contact)) return false;
+      Contact c = (Contact) o;
+      return c.id.equals(id)
+         && c.name.equals(name)
+         && c.tlf.equals(tlf);
+   }
+
+   @Override
+   public int hashCode() {
+      int result = 17;
+      result = 31 * result + (id == null ? 0 : id.hashCode());
+      result = 31 * result + name.hashCode();
+      result = 31 * result + tlf.hashCode();
+      return result;
+   }
+
+   
+   
+}


Property changes on: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Contact.java
___________________________________________________________________
Name: svn:executable
   + *

Added: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Customer.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Customer.java	                        (rev 0)
+++ core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Customer.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -0,0 +1,67 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors.  All third-party contributions are
+ * distributed under license by Red Hat Middleware LLC.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program 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 distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA  02110-1301  USA
+ */
+package org.hibernate.test.cache.infinispan.functional.cluster;
+
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * Company customer
+ * 
+ * @author Emmanuel Bernard
+ * @author Kabir Khan
+ */
+public class Customer implements Serializable {
+   Integer id;
+   String name;
+
+   private transient Set<Contact> contacts;
+
+   public Customer() {
+   }
+
+   public Integer getId() {
+      return id;
+   }
+
+   public void setId(Integer id) {
+      this.id = id;
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public void setName(String string) {
+      name = string;
+   }
+
+   public Set<Contact> getContacts() {
+      return contacts;
+   }
+
+   public void setContacts(Set<Contact> contacts) {
+      this.contacts = contacts;
+   }
+}


Property changes on: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/Customer.java
___________________________________________________________________
Name: svn:executable
   + *

Added: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeConnectionProviderImpl.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeConnectionProviderImpl.java	                        (rev 0)
+++ core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeConnectionProviderImpl.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -0,0 +1,85 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2007, Red Hat Middleware LLC or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors.  All third-party contributions are
+ * distributed under license by Red Hat Middleware LLC.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program 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 distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA  02110-1301  USA
+ */
+package org.hibernate.test.cache.infinispan.functional.cluster;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Properties;
+
+import org.hibernate.HibernateException;
+import org.hibernate.connection.ConnectionProvider;
+import org.hibernate.connection.ConnectionProviderFactory;
+
+/**
+ * A {@link ConnectionProvider} implementation adding JTA-style transactionality around the returned
+ * connections using the {@link DualNodeJtaTransactionManagerImpl}.
+ * 
+ * @author Brian Stansberry
+ */
+public class DualNodeConnectionProviderImpl implements ConnectionProvider {
+   private static ConnectionProvider actualConnectionProvider = ConnectionProviderFactory.newConnectionProvider();
+   private String nodeId;
+   private boolean isTransactional;
+
+   public static ConnectionProvider getActualConnectionProvider() {
+      return actualConnectionProvider;
+   }
+
+   public void configure(Properties props) throws HibernateException {
+      nodeId = props.getProperty(AbstractDualNodeTestCase.NODE_ID_PROP);
+      if (nodeId == null)
+         throw new HibernateException(AbstractDualNodeTestCase.NODE_ID_PROP + " not configured");
+   }
+
+   public Connection getConnection() throws SQLException {
+      DualNodeJtaTransactionImpl currentTransaction = DualNodeJtaTransactionManagerImpl
+               .getInstance(nodeId).getCurrentTransaction();
+      if (currentTransaction == null) {
+         isTransactional = false;
+         return actualConnectionProvider.getConnection();
+      } else {
+         isTransactional = true;
+         Connection connection = currentTransaction.getEnlistedConnection();
+         if (connection == null) {
+            connection = actualConnectionProvider.getConnection();
+            currentTransaction.enlistConnection(connection);
+         }
+         return connection;
+      }
+   }
+
+   public void closeConnection(Connection conn) throws SQLException {
+      if (!isTransactional) {
+         conn.close();
+      }
+   }
+
+   public void close() throws HibernateException {
+      actualConnectionProvider.close();
+   }
+
+   public boolean supportsAggressiveRelease() {
+      return true;
+   }
+}

Added: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeJtaTransactionImpl.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeJtaTransactionImpl.java	                        (rev 0)
+++ core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeJtaTransactionImpl.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -0,0 +1,159 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2007, Red Hat Middleware LLC or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors.  All third-party contributions are
+ * distributed under license by Red Hat Middleware LLC.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program 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 distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA  02110-1301  USA
+ */
+package org.hibernate.test.cache.infinispan.functional.cluster;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.LinkedList;
+
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.xa.XAResource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * SimpleJtaTransactionImpl variant that works with DualNodeTransactionManagerImpl.
+ * 
+ * @author Brian Stansberry
+ */
+public class DualNodeJtaTransactionImpl implements Transaction {
+   private static final Logger log = LoggerFactory.getLogger(DualNodeJtaTransactionImpl.class);
+
+   private int status;
+   private LinkedList synchronizations;
+   private Connection connection; // the only resource we care about is jdbc connection
+   private final DualNodeJtaTransactionManagerImpl jtaTransactionManager;
+
+   public DualNodeJtaTransactionImpl(DualNodeJtaTransactionManagerImpl jtaTransactionManager) {
+      this.jtaTransactionManager = jtaTransactionManager;
+      this.status = Status.STATUS_ACTIVE;
+   }
+
+   public int getStatus() {
+      return status;
+   }
+
+   public void commit() throws RollbackException, HeuristicMixedException,
+            HeuristicRollbackException, IllegalStateException, SystemException {
+
+      if (status == Status.STATUS_MARKED_ROLLBACK) {
+         log.trace("on commit, status was marked for rollback-only");
+         rollback();
+      } else {
+         status = Status.STATUS_PREPARING;
+
+         for (int i = 0; i < synchronizations.size(); i++) {
+            Synchronization s = (Synchronization) synchronizations.get(i);
+            s.beforeCompletion();
+         }
+
+         status = Status.STATUS_COMMITTING;
+
+         if (connection != null) {
+            try {
+               connection.commit();
+               connection.close();
+            } catch (SQLException sqle) {
+               status = Status.STATUS_UNKNOWN;
+               throw new SystemException();
+            }
+         }
+
+         status = Status.STATUS_COMMITTED;
+
+         for (int i = 0; i < synchronizations.size(); i++) {
+            Synchronization s = (Synchronization) synchronizations.get(i);
+            s.afterCompletion(status);
+         }
+
+         // status = Status.STATUS_NO_TRANSACTION;
+         jtaTransactionManager.endCurrent(this);
+      }
+   }
+
+   public void rollback() throws IllegalStateException, SystemException {
+      status = Status.STATUS_ROLLEDBACK;
+
+      if (connection != null) {
+         try {
+            connection.rollback();
+            connection.close();
+         } catch (SQLException sqle) {
+            status = Status.STATUS_UNKNOWN;
+            throw new SystemException();
+         }
+      }
+
+      if (synchronizations != null) {
+         for (int i = 0; i < synchronizations.size(); i++) {
+            Synchronization s = (Synchronization) synchronizations.get(i);
+            s.afterCompletion(status);
+         }
+      }
+
+      // status = Status.STATUS_NO_TRANSACTION;
+      jtaTransactionManager.endCurrent(this);
+   }
+
+   public void setRollbackOnly() throws IllegalStateException, SystemException {
+      status = Status.STATUS_MARKED_ROLLBACK;
+   }
+
+   public void registerSynchronization(Synchronization synchronization) throws RollbackException,
+            IllegalStateException, SystemException {
+      // todo : find the spec-allowable statuses during which synch can be registered...
+      if (synchronizations == null) {
+         synchronizations = new LinkedList();
+      }
+      synchronizations.add(synchronization);
+   }
+
+   public void enlistConnection(Connection connection) {
+      if (this.connection != null) {
+         throw new IllegalStateException("Connection already registered");
+      }
+      this.connection = connection;
+   }
+
+   public Connection getEnlistedConnection() {
+      return connection;
+   }
+
+   public boolean enlistResource(XAResource xaResource) throws RollbackException,
+            IllegalStateException, SystemException {
+      return false;
+   }
+
+   public boolean delistResource(XAResource xaResource, int i) throws IllegalStateException,
+            SystemException {
+      return false;
+   }
+}

Added: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeJtaTransactionManagerImpl.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeJtaTransactionManagerImpl.java	                        (rev 0)
+++ core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeJtaTransactionManagerImpl.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -0,0 +1,158 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2007, Red Hat Middleware LLC or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors.  All third-party contributions are
+ * distributed under license by Red Hat Middleware LLC.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program 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 distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA  02110-1301  USA
+ */
+package org.hibernate.test.cache.infinispan.functional.cluster;
+
+import java.util.Hashtable;
+
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Variant of SimpleJtaTransactionManagerImpl that doesn't use a VM-singleton, but rather a set of
+ * impls keyed by a node id.
+ * 
+ * @author Brian Stansberry
+ */
+public class DualNodeJtaTransactionManagerImpl implements TransactionManager {
+
+   private static final Logger log = LoggerFactory.getLogger(DualNodeJtaTransactionManagerImpl.class);
+
+   private static final Hashtable INSTANCES = new Hashtable();
+
+   private ThreadLocal currentTransaction = new ThreadLocal();
+   private String nodeId;
+
+   public synchronized static DualNodeJtaTransactionManagerImpl getInstance(String nodeId) {
+      DualNodeJtaTransactionManagerImpl tm = (DualNodeJtaTransactionManagerImpl) INSTANCES
+               .get(nodeId);
+      if (tm == null) {
+         tm = new DualNodeJtaTransactionManagerImpl(nodeId);
+         INSTANCES.put(nodeId, tm);
+      }
+      return tm;
+   }
+
+   public synchronized static void cleanupTransactions() {
+      for (java.util.Iterator it = INSTANCES.values().iterator(); it.hasNext();) {
+         TransactionManager tm = (TransactionManager) it.next();
+         try {
+            tm.suspend();
+         } catch (Exception e) {
+            log.error("Exception cleaning up TransactionManager " + tm);
+         }
+      }
+   }
+
+   public synchronized static void cleanupTransactionManagers() {
+      INSTANCES.clear();
+   }
+
+   private DualNodeJtaTransactionManagerImpl(String nodeId) {
+      this.nodeId = nodeId;
+   }
+
+   public int getStatus() throws SystemException {
+      Transaction tx = getCurrentTransaction();
+      return tx == null ? Status.STATUS_NO_TRANSACTION : tx.getStatus();
+   }
+
+   public Transaction getTransaction() throws SystemException {
+      return (Transaction) currentTransaction.get();
+   }
+
+   public DualNodeJtaTransactionImpl getCurrentTransaction() {
+      return (DualNodeJtaTransactionImpl) currentTransaction.get();
+   }
+
+   public void begin() throws NotSupportedException, SystemException {
+      currentTransaction.set(new DualNodeJtaTransactionImpl(this));
+   }
+
+   public Transaction suspend() throws SystemException {
+      DualNodeJtaTransactionImpl suspended = getCurrentTransaction();
+      log.trace(nodeId + ": Suspending " + suspended + " for thread "
+               + Thread.currentThread().getName());
+      currentTransaction.set(null);
+      return suspended;
+   }
+
+   public void resume(Transaction transaction) throws InvalidTransactionException,
+            IllegalStateException, SystemException {
+      currentTransaction.set((DualNodeJtaTransactionImpl) transaction);
+      log.trace(nodeId + ": Resumed " + transaction + " for thread "
+               + Thread.currentThread().getName());
+   }
+
+   public void commit() throws RollbackException, HeuristicMixedException,
+            HeuristicRollbackException, SecurityException, IllegalStateException, SystemException {
+      Transaction tx = getCurrentTransaction();
+      if (tx == null) {
+         throw new IllegalStateException("no current transaction to commit");
+      }
+      tx.commit();
+   }
+
+   public void rollback() throws IllegalStateException, SecurityException, SystemException {
+      Transaction tx = getCurrentTransaction();
+      if (tx == null) {
+         throw new IllegalStateException("no current transaction");
+      }
+      tx.rollback();
+   }
+
+   public void setRollbackOnly() throws IllegalStateException, SystemException {
+      Transaction tx = getCurrentTransaction();
+      if (tx == null) {
+         throw new IllegalStateException("no current transaction");
+      }
+      tx.setRollbackOnly();
+   }
+
+   public void setTransactionTimeout(int i) throws SystemException {
+   }
+
+   void endCurrent(DualNodeJtaTransactionImpl transaction) {
+      if (transaction == currentTransaction.get()) {
+         currentTransaction.set(null);
+      }
+   }
+
+   public String toString() {
+      StringBuffer sb = new StringBuffer(getClass().getName());
+      sb.append("[nodeId=");
+      sb.append(nodeId);
+      sb.append("]");
+      return sb.toString();
+   }
+}

Added: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeTransactionManagerLookup.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeTransactionManagerLookup.java	                        (rev 0)
+++ core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeTransactionManagerLookup.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -0,0 +1,55 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2007, Red Hat Middleware LLC or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors.  All third-party contributions are
+ * distributed under license by Red Hat Middleware LLC.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program 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 distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA  02110-1301  USA
+ */
+package org.hibernate.test.cache.infinispan.functional.cluster;
+
+import java.util.Properties;
+import javax.transaction.TransactionManager;
+import javax.transaction.Transaction;
+
+import org.hibernate.transaction.TransactionManagerLookup;
+import org.hibernate.HibernateException;
+
+/**
+ * SimpleJtaTransactionManagerLookupImpl subclass that finds a different DualNodeTransactionManager
+ * based on the value of property {@link DualNodeTestUtil#NODE_ID_PROP}.
+ * 
+ * @author Brian Stansberry
+ */
+public class DualNodeTransactionManagerLookup implements TransactionManagerLookup {
+
+   public TransactionManager getTransactionManager(Properties props) throws HibernateException {
+      String nodeId = props.getProperty(AbstractDualNodeTestCase.NODE_ID_PROP);
+      if (nodeId == null)
+         throw new HibernateException(AbstractDualNodeTestCase.NODE_ID_PROP + " not configured");
+      return DualNodeJtaTransactionManagerImpl.getInstance(nodeId);
+   }
+
+   public String getUserTransactionName() {
+      throw new UnsupportedOperationException("jndi currently not implemented for these tests");
+   }
+
+   public Object getTransactionIdentifier(Transaction transaction) {
+      return transaction;
+   }
+}

Added: core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/EntityCollectionInvalidationTestCase.java
===================================================================
--- core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/EntityCollectionInvalidationTestCase.java	                        (rev 0)
+++ core/branches/INFINISPAN/cache-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/EntityCollectionInvalidationTestCase.java	2009-08-05 18:31:51 UTC (rev 17234)
@@ -0,0 +1,358 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This 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 software 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.hibernate.test.cache.infinispan.functional.cluster;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.transaction.TransactionManager;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.cache.CacheKey;
+import org.infinispan.Cache;
+import org.infinispan.manager.CacheManager;
+import org.infinispan.marshall.MarshalledValue;
+import org.infinispan.notifications.Listener;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryVisited;
+import org.infinispan.notifications.cachelistener.event.CacheEntryVisitedEvent;
+import org.jboss.util.collection.ConcurrentSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * EntityReplicationTestCase.
+ * 
+ * @author Galder Zamarreño
+ */
+public class EntityCollectionInvalidationTestCase extends AbstractDualNodeTestCase {
+   private static final Logger log = LoggerFactory.getLogger(EntityCollectionInvalidationTestCase.class);
+   private static final long SLEEP_TIME = 50l;
+   private static final Integer CUSTOMER_ID = new Integer(1);
+   static int test = 0;
+
+   public EntityCollectionInvalidationTestCase(String string) {
+      super(string);
+   }
+
+   protected String getEntityCacheConfigName() {
+      return "entity";
+   }
+
+   public void testAll() throws Exception {
+      log.info("*** testAll()");
+
+      // Bind a listener to the "local" cache
+      // Our region factory makes its CacheManager available to us
+      CacheManager localManager = ClusterAwareRegionFactory
+               .getCacheManager(AbstractDualNodeTestCase.LOCAL);
+      // Cache localCache = localManager.getCache("entity");
+      Cache localCustomerCache = localManager.getCache(Customer.class.getName());
+      Cache localContactCache = localManager.getCache(Contact.class.getName());
+      Cache localCollectionCache = localManager.getCache(Customer.class.getName() + ".contacts");
+      MyListener localListener = new MyListener("local");
+      localCustomerCache.addListener(localListener);
+      localContactCache.addListener(localListener);
+      localCollectionCache.addListener(localListener);
+
+      TransactionManager localTM = DualNodeJtaTransactionManagerImpl.getInstance(AbstractDualNodeTestCase.LOCAL);
+
+      // Bind a listener to the "remote" cache
+      CacheManager remoteManager = ClusterAwareRegionFactory.getCacheManager(AbstractDualNodeTestCase.REMOTE);
+      Cache remoteCustomerCache = remoteManager.getCache(Customer.class.getName());
+      Cache remoteContactCache = remoteManager.getCache(Contact.class.getName());
+      Cache remoteCollectionCache = remoteManager.getCache(Customer.class.getName() + ".contacts");
+      MyListener remoteListener = new MyListener("remote");
+      remoteCustomerCache.addListener(remoteListener);
+      remoteContactCache.addListener(remoteListener);
+      remoteCollectionCache.addListener(remoteListener);
+
+      TransactionManager remoteTM = DualNodeJtaTransactionManagerImpl
+               .getInstance(AbstractDualNodeTestCase.REMOTE);
+
+      SessionFactory localFactory = getEnvironment().getSessionFactory();
+      SessionFactory remoteFactory = getSecondNodeEnvironment().getSessionFactory();
+
+      try {
+         assertTrue(remoteListener.isEmpty());
+         assertTrue(localListener.isEmpty());
+         
+         log.debug("Create node 0");
+         IdContainer ids = createCustomer(localFactory, localTM);
+
+         assertTrue(remoteListener.isEmpty());
+         assertTrue(localListener.isEmpty());
+         
+         // Sleep a bit to let async commit propagate. Really just to
+         // help keep the logs organized for debugging any issues
+         sleep(SLEEP_TIME);
+
+         log.debug("Find node 0");
+         // This actually brings the collection into the cache
+         getCustomer(ids.customerId, localFactory, localTM);
+
+         sleep(SLEEP_TIME);
+
+         // Now the collection is in the cache so, the 2nd "get"
+         // should read everything from the cache
+         log.debug("Find(2) node 0");
+         localListener.clear();
+         getCustomer(ids.customerId, localFactory, localTM);
+
+         // Check the read came from the cache
+         log.debug("Check cache 0");
+         assertLoadedFromCache(localListener, ids.customerId, ids.contactIds);
+
+         log.debug("Find node 1");
+         // This actually brings the collection into the cache since invalidation is in use
+         getCustomer(ids.customerId, remoteFactory, remoteTM);
+         
+         // Now the collection is in the cache so, the 2nd "get"
+         // should read everything from the cache
+         log.debug("Find(2) node 1");
+         remoteListener.clear();
+         getCustomer(ids.customerId, remoteFactory, remoteTM);
+
+         // Check the read came from the cache
+         log.debug("Check cache 1");
+         assertLoadedFromCache(remoteListener, ids.customerId, ids.contactIds);
+
+         // Modify customer in remote
+         remoteListener.clear();
+         ids = modifyCustomer(ids.customerId, remoteFactory, remoteTM);
+         assertLoadedFromCache(remoteListener, ids.customerId, ids.contactIds);         
+
+         // After modification, local cache should have been invalidated and hence should be empty
+         assertTrue(localCollectionCache.isEmpty());
+         assertTrue(localCustomerCache.isEmpty());
+         assertTrue(localContactCache.isEmpty());         
+      } catch (Exception e) {
+         log.error("Error", e);
+         throw e;
+      } finally {
+         // cleanup the db
+         log.debug("Cleaning up");
+         cleanup(localFactory, localTM);
+      }
+   }
+
+   private IdContainer createCustomer(SessionFactory sessionFactory, TransactionManager tm)
+            throws Exception {
+      log.debug("CREATE CUSTOMER");
+
+      tm.begin();
+
+      try {
+         Session session = sessionFactory.getCurrentSession();
+         Customer customer = new Customer();
+         customer.setName("JBoss");
+         Set<Contact> contacts = new HashSet<Contact>();
+
+         Contact kabir = new Contact();
+         kabir.setCustomer(customer);
+         kabir.setName("Kabir");
+         kabir.setTlf("1111");
+         contacts.add(kabir);
+
+         Contact bill = new Contact();
+         bill.setCustomer(customer);
+         bill.setName("Bill");
+         bill.setTlf("2222");
+         contacts.add(bill);
+
+         customer.setContacts(contacts);
+
+         session.save(customer);
+         tm.commit();
+
+         IdContainer ids = new IdContainer();
+         ids.customerId = customer.getId();
+         Set contactIds = new HashSet();
+         contactIds.add(kabir.getId());
+         contactIds.add(bill.getId());
+         ids.contactIds = contactIds;
+
+         return ids;
+      } catch (Exception e) {
+         log.error("Caught exception creating customer", e);
+         try {
+            tm.rollback();
+         } catch (Exception e1) {
+            log.error("Exception rolling back txn", e1);
+         }
+         throw e;
+      } finally {
+         log.debug("CREATE CUSTOMER -  END");
+      }
+   }
+
+   private Customer getCustomer(Integer id, SessionFactory sessionFactory, TransactionManager tm) throws Exception {
+      log.debug("Find customer with id=" + id);
+      tm.begin();
+      try {
+         Session session = sessionFactory.getCurrentSession();
+         Customer customer = doGetCustomer(id, session, tm);
+         tm.commit();
+         return customer;
+      } catch (Exception e) {
+         try {
+            tm.rollback();
+         } catch (Exception e1) {
+            log.error("Exception rolling back txn", e1);
+         }
+         throw e;
+      } finally {
+         log.debug("Find customer ended.");
+      }
+   }
+   
+   private Customer doGetCustomer(Integer id, Session session, TransactionManager tm) throws Exception {
+      Customer customer = (Customer) session.get(Customer.class, id);
+      // Access all the contacts
+      for (Iterator it = customer.getContacts().iterator(); it.hasNext();) {
+         ((Contact) it.next()).getName();
+      }
+      return customer;
+   }
+   
+   private IdContainer modifyCustomer(Integer id, SessionFactory sessionFactory, TransactionManager tm) throws Exception {
+      log.debug("Modify customer with id=" + id);
+      tm.begin();
+      try {
+         Session session = sessionFactory.getCurrentSession();
+         IdContainer ids = new IdContainer();
+         Set contactIds = new HashSet();
+         Customer customer = doGetCustomer(id, session, tm);
+         customer.setName("NewJBoss");
+         ids.customerId = customer.getId();
+         
+         Set<Contact> contacts = customer.getContacts();
+         for (Contact c : contacts) {
+            if (c.name.equals("Kabir")) {
+               contacts.remove(c);
+            } else {
+               contactIds.add(c.getId());
+            }
+         }
+         ids.contactIds = contactIds;
+         customer.setContacts(contacts);
+         session.save(customer);
+         tm.commit();
+         return ids;
+      } catch (Exception e) {
+         try {
+            tm.rollback();
+         } catch (Exception e1) {
+            log.error("Exception rolling back txn", e1);
+         }
+         throw e;
+      } finally {
+         log.debug("Find customer ended.");
+      }
+   }
+
+   private void cleanup(SessionFactory sessionFactory, TransactionManager tm) throws Exception {
+      tm.begin();
+      try {
+         Session session = sessionFactory.getCurrentSession();
+         Customer c = (Customer) session.get(Customer.class, CUSTOMER_ID);
+         if (c != null) {
+            Set contacts = c.getContacts();
+            for (Iterator it = contacts.iterator(); it.hasNext();)
+               session.delete(it.next());
+            c.setContacts(null);
+            session.delete(c);
+         }
+
+         tm.commit();
+      } catch (Exception e) {
+         try {
+            tm.rollback();
+         } catch (Exception e1) {
+            log.error("Exception rolling back txn", e1);
+         }
+         log.error("Caught exception in cleanup", e);
+      }
+   }
+
+   private void assertLoadedFromCache(MyListener listener, Integer custId, Set contactIds) {
+      assertTrue("Customer#" + custId + " was in cache", listener.visited.contains("Customer#"
+               + custId));
+      for (Iterator it = contactIds.iterator(); it.hasNext();) {
+         Integer contactId = (Integer) it.next();
+         assertTrue("Contact#" + contactId + " was in cache", listener.visited.contains("Contact#"
+                  + contactId));
+         assertTrue("Contact#" + contactId + " was in cache", listener.visited.contains("Contact#"
+                  + contactId));
+      }
+      assertTrue("Customer.contacts" + custId + " was in cache", listener.visited
+               .contains("Customer.contacts#" + custId));
+   }
+
+   @Listener
+   public static class MyListener {
+      private static final Logger log = LoggerFactory.getLogger(MyListener.class);
+      private Set<String> visited = new ConcurrentSet<String>();
+      private final String name;
+      
+      public MyListener(String name) {
+         this.name = name;
+      }      
+
+      public void clear() {
+         visited.clear();
+      }
+      
+      public boolean isEmpty() {
+         return visited.isEmpty();
+      }
+
+      @CacheEntryVisited
+      public void nodeVisited(CacheEntryVisitedEvent event) {
+         log.debug(event.toString());
+
+         if (!event.isPre()) {
+            MarshalledValue mv = (MarshalledValue) event.getKey();
+            CacheKey cacheKey = (CacheKey) mv.get();
+            Integer primKey = (Integer) cacheKey.getKey();
+            String key = (String) cacheKey.getEntityOrRoleName() + '#' + primKey;
+            log.debug("MyListener[" + name +"] - Visiting key " + key);
+            // String name = fqn.toString();
+            String token = ".functional.cluster.";
+            int index = key.indexOf(token);
+            if (index > -1) {
+               index += token.length();
+               key = key.substring(index);
+               log.debug("MyListener[" + name +"] - recording visit to " + key);
+               visited.add(key);
+            }            
+         }
+      }
+   }
+
+   private class IdContainer {
+      Integer customerId;
+      Set<Integer> contactIds;
+   }
+
+}



More information about the hibernate-commits mailing list