/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2009-2011, Red Hat Inc. 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 Inc.
*
* 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 javax.transaction.TransactionManager;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.infinispan.Cache;
import org.infinispan.manager.CacheContainer;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryVisited;
import org.infinispan.notifications.cachelistener.event.CacheEntryVisitedEvent;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import org.jboss.util.collection.ConcurrentSet;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cache.spi.CacheKey;
import org.hibernate.cache.infinispan.util.CacheHelper;
import org.junit.Test;
import org.hibernate.test.cache.infinispan.functional.Contact;
import org.hibernate.test.cache.infinispan.functional.Customer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* EntityCollectionInvalidationTestCase.
*
* @author Galder ZamarreƱo
* @since 3.5
*/
public class EntityCollectionInvalidationTestCase extends DualNodeTestCase {
private static final Log log = LogFactory.getLog( EntityCollectionInvalidationTestCase.class );
private static final long SLEEP_TIME = 50l;
private static final Integer CUSTOMER_ID = new Integer( 1 );
static int test = 0;
@Test
public void testAll() throws Exception {
log.info( "*** testAll()" );
// Bind a listener to the "local" cache
// Our region factory makes its CacheManager available to us
CacheContainer localManager = ClusterAwareRegionFactory.getCacheManager( DualNodeTestCase.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( DualNodeTestCase.LOCAL );
// Bind a listener to the "remote" cache
CacheContainer remoteManager = ClusterAwareRegionFactory.getCacheManager( DualNodeTestCase.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( DualNodeTestCase.REMOTE );
SessionFactory localFactory = sessionFactory();
SessionFactory remoteFactory = secondNodeEnvironment().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 );
sleep( 250 );
assertLoadedFromCache( remoteListener, ids.customerId, ids.contactIds );
// After modification, local cache should have been invalidated and hence should be empty
assertEquals( 0, getValidKeyCount( localCollectionCache.keySet() ) );
assertEquals( 0, getValidKeyCount( localCustomerCache.keySet() ) );
}
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 ) {
contactIds.add( c.getId() );
}
Contact contact = contacts.iterator().next();
contacts.remove( contact );
contactIds.remove( contact.getId() );
ids.contactIds = contactIds;
contact.setCustomer( null );
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 )
);
}
protected int getValidKeyCount(Set keys) {
int result = 0;
for ( Object key : keys ) {
if ( !(CacheHelper.isEvictAllNotification( key )) ) {
result++;
}
}
return result;
}
@Listener
public static class MyListener {
private static final Log log = LogFactory.getLog( 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() ) {
CacheKey cacheKey = (CacheKey) event.getKey();
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.";
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;
}
}