/*
* JBoss, Home of Professional Open Source.
* Copyright 2009, Red Hat, Inc. and/or it's affiliates, 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.hibernate.cache.infinispan.util.CacheHelper;
import org.hibernate.test.cache.infinispan.functional.Contact;
import org.hibernate.test.cache.infinispan.functional.Customer;
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.jboss.util.collection.ConcurrentSet;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* 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;
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
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 = 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);
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;
}
}