/*
* 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.classloader;
import javax.transaction.TransactionManager;
import org.infinispan.Cache;
import org.infinispan.manager.CacheContainer;
import org.infinispan.manager.EmbeddedCacheManager;
import org.jboss.logging.Logger;
import org.hibernate.SessionFactory;
import org.hibernate.cache.internal.StandardQueryCache;
import org.hibernate.cache.infinispan.InfinispanRegionFactory;
import org.hibernate.cfg.Configuration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.hibernate.test.cache.infinispan.functional.cluster.ClusterAwareRegionFactory;
import org.hibernate.test.cache.infinispan.functional.cluster.DualNodeJtaTransactionManagerImpl;
import org.hibernate.test.cache.infinispan.functional.cluster.DualNodeTestCase;
/**
* Tests entity and query caching when class of objects being cached are not visible to Infinispan's classloader. Also serves as a
* general integration test.
* <p/>
* This test stores an object (AccountHolder) that isn't visible to the Infinispan classloader in the cache in two places: 1) As
* part of the value tuple in an Account entity 2) As part of the FQN in a query cache entry (see query in
* ClassLoaderTestDAO.getBranch())
*
* @author Galder ZamarreƱo
* @since 3.5
*/
public class IsolatedClassLoaderTest extends DualNodeTestCase {
private static final Logger log = Logger.getLogger( IsolatedClassLoaderTest.class );
protected static final long SLEEP_TIME = 300L;
private Cache localQueryCache;
private CacheAccessListener localQueryListener;
private Cache remoteQueryCache;
private CacheAccessListener remoteQueryListener;
private static ClassLoader originalTCCL;
@BeforeClass
public static void prepareClassLoader() {
final String packageName = IsolatedClassLoaderTest.class.getPackage().getName();
final String[] classes = new String[] { packageName + ".Account", packageName + ".AccountHolder" };
originalTCCL = Thread.currentThread().getContextClassLoader();
// Most likely, it will point to system classloader
ClassLoader parent = originalTCCL == null ? IsolatedClassLoaderTest.class.getClassLoader() : originalTCCL;
// First, create a classloader where classes won't be found
ClassLoader selectedTCCL = new SelectedClassnameClassLoader(null, null, classes, parent);
// Now, make the class visible to the test driver
SelectedClassnameClassLoader visible = new SelectedClassnameClassLoader(classes, null, null, selectedTCCL);
Thread.currentThread().setContextClassLoader(visible);
}
@AfterClass
public static void resetClassLoader() {
ClusterAwareRegionFactory.clearCacheManagers();
DualNodeJtaTransactionManagerImpl.cleanupTransactions();
DualNodeJtaTransactionManagerImpl.cleanupTransactionManagers();
Thread.currentThread().setContextClassLoader( originalTCCL );
}
@Override
public String[] getMappings() {
return new String[] {"cache/infinispan/functional/classloader/Account.hbm.xml"};
}
@Override
protected void standardConfigure(Configuration cfg) {
super.standardConfigure( cfg );
cfg.setProperty( InfinispanRegionFactory.QUERY_CACHE_RESOURCE_PROP, "replicated-query" );
cfg.setProperty( "hibernate.cache.infinispan.AccountRegion.cfg", "replicated-query" );
}
@Override
protected void cleanupTransactionManagement() {
// Don't clean up the managers, just the transactions
// Managers are still needed by the long-lived caches
DualNodeJtaTransactionManagerImpl.cleanupTransactions();
}
@Override
protected void cleanupTest() throws Exception {
try {
// Clear the local account cache
sessionFactory().getCache().evictEntityRegion(Account.class.getName());
if ( localQueryCache != null && localQueryListener != null ) {
localQueryCache.removeListener( localQueryListener );
}
if ( remoteQueryCache != null && remoteQueryListener != null ) {
remoteQueryCache.removeListener( remoteQueryListener );
}
}
finally {
super.cleanupTest();
}
}
@Test
public void testIsolatedSetup() throws Exception {
// Bind a listener to the "local" cache
// Our region factory makes its CacheManager available to us
CacheContainer localManager = ClusterAwareRegionFactory.getCacheManager( DualNodeTestCase.LOCAL );
Cache localReplicatedCache = localManager.getCache( "replicated-entity" );
// Bind a listener to the "remote" cache
CacheContainer remoteManager = ClusterAwareRegionFactory.getCacheManager( DualNodeTestCase.REMOTE );
Cache remoteReplicatedCache = remoteManager.getCache( "replicated-entity" );
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader( cl.getParent() );
log.info( "TCCL is " + cl.getParent() );
Account acct = new Account();
acct.setAccountHolder( new AccountHolder() );
try {
localReplicatedCache.put( "isolated1", acct );
// With lazy deserialization, retrieval in remote forces class resolution
remoteReplicatedCache.get( "isolated1" );
fail( "Should not have succeeded in putting acct -- classloader not isolated" );
}
catch (Exception e) {
if ( e.getCause() instanceof ClassNotFoundException ) {
log.info( "Caught exception as desired", e );
}
else {
throw new IllegalStateException( "Unexpected exception", e );
}
}
Thread.currentThread().setContextClassLoader( cl );
log.info( "TCCL is " + cl );
localReplicatedCache.put( "isolated2", acct );
assertEquals( acct.getClass().getName(), remoteReplicatedCache.get( "isolated2" ).getClass().getName() );
}
@Test
public void testClassLoaderHandlingNamedQueryRegion() throws Exception {
rebuildSessionFactory();
queryTest( true );
}
@Test
public void testClassLoaderHandlingStandardQueryCache() throws Exception {
rebuildSessionFactory();
queryTest( false );
}
protected void queryTest(boolean useNamedRegion) throws Exception {
// Bind a listener to the "local" cache
// Our region factory makes its CacheManager available to us
EmbeddedCacheManager localManager = ClusterAwareRegionFactory.getCacheManager( DualNodeTestCase.LOCAL );
// Bind a listener to the "remote" cache
EmbeddedCacheManager remoteManager = ClusterAwareRegionFactory.getCacheManager( DualNodeTestCase.REMOTE );
String cacheName;
if ( useNamedRegion ) {
cacheName = "AccountRegion"; // As defined by ClassLoaderTestDAO via calls to query.setCacheRegion
// Define cache configurations for region early to avoid ending up with local caches for this region
localManager.defineConfiguration(
cacheName, "replicated-query", new org.infinispan.config.Configuration()
);
remoteManager.defineConfiguration(
cacheName, "replicated-query", new org.infinispan.config.Configuration()
);
}
else {
cacheName = "replicated-query";
}
localQueryCache = localManager.getCache( cacheName );
localQueryListener = new CacheAccessListener();
localQueryCache.addListener( localQueryListener );
TransactionManager localTM = DualNodeJtaTransactionManagerImpl.getInstance( DualNodeTestCase.LOCAL );
remoteQueryCache = remoteManager.getCache( cacheName );
remoteQueryListener = new CacheAccessListener();
remoteQueryCache.addListener( remoteQueryListener );
TransactionManager remoteTM = DualNodeJtaTransactionManagerImpl.getInstance( DualNodeTestCase.REMOTE );
SessionFactory localFactory = sessionFactory();
SessionFactory remoteFactory = secondNodeEnvironment().getSessionFactory();
ClassLoaderTestDAO dao0 = new ClassLoaderTestDAO( localFactory, localTM );
ClassLoaderTestDAO dao1 = new ClassLoaderTestDAO( remoteFactory, remoteTM );
// Initial ops on node 0
setupEntities( dao0 );
String branch = "63088";
// Query on post code count
assertEquals( branch + " has correct # of accounts", 6, dao0.getCountForBranch( branch, useNamedRegion ) );
assertEquals( "Query cache used", 1, localQueryListener.getSawRegionModificationCount() );
localQueryListener.clearSawRegionModification();
// log.info("First query (get count for branch + " + branch + " ) on node0 done, contents of local query cache are: " + TestingUtil.printCache(localQueryCache));
// Sleep a bit to allow async repl to happen
sleep( SLEEP_TIME );
assertEquals( "Query cache used", 1, remoteQueryListener.getSawRegionModificationCount() );
remoteQueryListener.clearSawRegionModification();
// Do query again from node 1
log.info( "Repeat first query (get count for branch + " + branch + " ) on remote node" );
assertEquals( "63088 has correct # of accounts", 6, dao1.getCountForBranch( branch, useNamedRegion ) );
assertEquals( "Query cache used", 1, remoteQueryListener.getSawRegionModificationCount() );
remoteQueryListener.clearSawRegionModification();
sleep( SLEEP_TIME );
assertEquals( "Query cache used", 1, localQueryListener.getSawRegionModificationCount() );
localQueryListener.clearSawRegionModification();
log.info( "First query on node 1 done" );
// Sleep a bit to allow async repl to happen
sleep( SLEEP_TIME );
// Do some more queries on node 0
log.info( "Do query Smith's branch" );
assertEquals( "Correct branch for Smith", "94536", dao0.getBranch( dao0.getSmith(), useNamedRegion ) );
log.info( "Do query Jone's balance" );
assertEquals( "Correct high balances for Jones", 40, dao0.getTotalBalance( dao0.getJones(), useNamedRegion ) );
assertEquals( "Query cache used", 2, localQueryListener.getSawRegionModificationCount() );
localQueryListener.clearSawRegionModification();
// // Clear the access state
// localQueryListener.getSawRegionAccess("???");
log.info( "Second set of queries on node0 done" );
// Sleep a bit to allow async repl to happen
sleep( SLEEP_TIME );
// Check if the previous queries replicated
assertEquals( "Query cache remotely modified", 2, remoteQueryListener.getSawRegionModificationCount() );
remoteQueryListener.clearSawRegionModification();
log.info( "Repeat second set of queries on node1" );
// Do queries again from node 1
log.info( "Again query Smith's branch" );
assertEquals( "Correct branch for Smith", "94536", dao1.getBranch( dao1.getSmith(), useNamedRegion ) );
log.info( "Again query Jone's balance" );
assertEquals( "Correct high balances for Jones", 40, dao1.getTotalBalance( dao1.getJones(), useNamedRegion ) );
// Should be no change; query was already there
assertEquals( "Query cache modified", 0, remoteQueryListener.getSawRegionModificationCount() );
assertEquals( "Query cache accessed", 2, remoteQueryListener.getSawRegionAccessCount() );
remoteQueryListener.clearSawRegionAccess();
log.info( "Second set of queries on node1 done" );
// allow async to propagate
sleep( SLEEP_TIME );
// Modify underlying data on node 1
modifyEntities( dao1 );
// allow async timestamp change to propagate
sleep( SLEEP_TIME );
// Confirm query results are correct on node 0
assertEquals( "63088 has correct # of accounts", 7, dao0.getCountForBranch( "63088", useNamedRegion ) );
assertEquals( "Correct branch for Smith", "63088", dao0.getBranch( dao0.getSmith(), useNamedRegion ) );
assertEquals( "Correct high balances for Jones", 50, dao0.getTotalBalance( dao0.getJones(), useNamedRegion ) );
log.info( "Third set of queries on node0 done" );
}
protected void setupEntities(ClassLoaderTestDAO dao) throws Exception {
dao.cleanup();
dao.createAccount( dao.getSmith(), new Integer( 1001 ), new Integer( 5 ), "94536" );
dao.createAccount( dao.getSmith(), new Integer( 1002 ), new Integer( 15 ), "94536" );
dao.createAccount( dao.getSmith(), new Integer( 1003 ), new Integer( 20 ), "94536" );
dao.createAccount( dao.getJones(), new Integer( 2001 ), new Integer( 5 ), "63088" );
dao.createAccount( dao.getJones(), new Integer( 2002 ), new Integer( 15 ), "63088" );
dao.createAccount( dao.getJones(), new Integer( 2003 ), new Integer( 20 ), "63088" );
dao.createAccount( dao.getBarney(), new Integer( 3001 ), new Integer( 5 ), "63088" );
dao.createAccount( dao.getBarney(), new Integer( 3002 ), new Integer( 15 ), "63088" );
dao.createAccount( dao.getBarney(), new Integer( 3003 ), new Integer( 20 ), "63088" );
log.info( "Standard entities created" );
}
protected void resetRegionUsageState(CacheAccessListener localListener, CacheAccessListener remoteListener) {
String stdName = StandardQueryCache.class.getName();
String acctName = Account.class.getName();
localListener.getSawRegionModification( stdName );
localListener.getSawRegionModification( acctName );
localListener.getSawRegionAccess( stdName );
localListener.getSawRegionAccess( acctName );
remoteListener.getSawRegionModification( stdName );
remoteListener.getSawRegionModification( acctName );
remoteListener.getSawRegionAccess( stdName );
remoteListener.getSawRegionAccess( acctName );
log.info( "Region usage state cleared" );
}
protected void modifyEntities(ClassLoaderTestDAO dao) throws Exception {
dao.updateAccountBranch( 1001, "63088" );
dao.updateAccountBalance( 2001, 15 );
log.info( "Entities modified" );
}
}