Add tests of isolated-classloader environments

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@14381 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Brian Stansberry 2008-02-28 19:42:08 +00:00
parent ed518d42c0
commit ada6552638
15 changed files with 1728 additions and 14 deletions

View File

@ -16,6 +16,8 @@
package org.hibernate.test.cache.jbc2.functional;
import javax.transaction.TransactionManager;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Mappings;
@ -23,6 +25,7 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.junit.functional.ExecutionEnvironment;
import org.hibernate.test.cache.jbc2.functional.util.DualNodeConnectionProviderImpl;
import org.hibernate.test.cache.jbc2.functional.util.DualNodeJtaTransactionManagerImpl;
import org.hibernate.test.cache.jbc2.functional.util.DualNodeTestUtil;
import org.hibernate.test.cache.jbc2.functional.util.DualNodeTransactionManagerLookup;
import org.hibernate.test.cache.jbc2.functional.util.TestCacheInstanceManager;
@ -138,11 +141,21 @@ public abstract class DualNodeTestCaseBase extends CacheTestCaseBase
@Override
protected void cleanupTest() throws Exception
{
super.cleanupTest();
try {
super.cleanupTest();
log.info( "Destroying second node locally managed execution env" );
secondNodeEnvironment.complete();
secondNodeEnvironment = null;
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() {

View File

@ -0,0 +1,46 @@
<?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) 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
-->
<hibernate-mapping
package="org.hibernate.test.cache.jbc2.functional.classloader">
<class name="Account" table="Accounts">
<cache usage="transactional"/>
<id name="id">
<generator class="assigned"/>
</id>
<property name="branch" not-null="true"/>
<property name="balance" not-null="true"/>
<property name="accountHolder" type="serializable" not-null="true"/>
</class>
</hibernate-mapping>

View File

@ -0,0 +1,142 @@
/*
* 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.jbc2.functional.classloader;
import java.io.Serializable;
/**
* Comment
*
* @author Brian Stansberry
* @version $Revision: 60233 $
*/
// @NamedQuery(name="account.totalbalance.default",query="select account.balance from Account as account where account.accountHolder = ?1",
// hints={@QueryHint(name="org.hibernate.cacheable",value="true")}),
// @NamedQuery(name="account.totalbalance.namedregion",query="select account.balance from Account as account where account.accountHolder = ?1",
// hints={@QueryHint(name="org.hibernate.cacheRegion",value="AccountRegion"),
// @QueryHint(name="org.hibernate.cacheable",value="true")
// }),
// @NamedQuery(name="account.branch.default",query="select account.branch from Account as account where account.accountHolder = ?1",
// hints={@QueryHint(name="org.hibernate.cacheable",value="true")}),
// @NamedQuery(name="account.branch.namedregion",query="select account.branch from Account as account where account.accountHolder = ?1",
// hints={@QueryHint(name="org.hibernate.cacheRegion",value="AccountRegion"),
// @QueryHint(name="org.hibernate.cacheable",value="true")
// }),
// @NamedQuery(name="account.bybranch.default",query="select account from Account as account where account.branch = ?1",
// hints={@QueryHint(name="org.hibernate.cacheable",value="true")}),
// @NamedQuery(name="account.bybranch.namedregion",query="select account from Account as account where account.branch = ?1",
// hints={@QueryHint(name="org.hibernate.cacheRegion",value="AccountRegion"),
// @QueryHint(name="org.hibernate.cacheable",value="true")
// })
public class Account implements Serializable
{
private static final long serialVersionUID = 1L;
private Integer id;
private AccountHolder accountHolder;
private Integer balance;
private String branch;
public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this.id = id;
}
public AccountHolder getAccountHolder()
{
return accountHolder;
}
public void setAccountHolder(AccountHolder accountHolder)
{
this.accountHolder = accountHolder;
}
public Integer getBalance()
{
return balance;
}
public void setBalance(Integer balance)
{
this.balance = balance;
}
public String getBranch()
{
return branch;
}
public void setBranch(String branch)
{
this.branch = branch;
}
public boolean equals(Object obj)
{
if (obj == this) return true;
if (!(obj instanceof Account)) return false;
Account acct = (Account)obj;
if (!safeEquals(id, acct.id)) return false;
if (!safeEquals(branch, acct.branch)) return false;
if (!safeEquals(balance, acct.balance)) return false;
if (!safeEquals(accountHolder, acct.accountHolder)) return false;
return true;
}
public int hashCode( )
{
int result = 17;
result = result * 31 + safeHashCode(id);
result = result * 31 + safeHashCode(branch);
result = result * 31 + safeHashCode(balance);
result = result * 31 + safeHashCode(accountHolder);
return result;
}
public String toString()
{
StringBuffer sb = new StringBuffer(getClass().getName());
sb.append("[id=");
sb.append(id);
sb.append(",branch=");
sb.append(branch);
sb.append(",balance=");
sb.append(balance);
sb.append(",accountHolder=");
sb.append(accountHolder);
sb.append("]");
return sb.toString();
}
private static int safeHashCode(Object obj) {
return obj == null ? 0 : obj.hashCode();
}
private static boolean safeEquals(Object a, Object b) {
return (a == b || (a != null && a.equals(b)));
}
}

View File

@ -0,0 +1,97 @@
/*
* 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.jbc2.functional.classloader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
/**
* Comment
*
* @author Brian Stansberry
* @version $Revision: 60233 $
*/
public class AccountHolder implements Serializable
{
private static final long serialVersionUID = 1L;
private String lastName;
private String ssn;
private transient boolean deserialized;
public AccountHolder( ) {
this("Stansberry", "123-456-7890");
}
public AccountHolder(String lastName, String ssn)
{
this.lastName = lastName;
this.ssn = ssn;
}
public String getLastName( ) { return this.lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getSsn( ) { return ssn; }
public void setSsn(String ssn) { this.ssn = ssn; }
public boolean equals(Object obj)
{
if (obj == this) return true;
if (!(obj instanceof AccountHolder)) return false;
AccountHolder pk = (AccountHolder)obj;
if (!lastName.equals(pk.lastName)) return false;
if (!ssn.equals(pk.ssn)) return false;
return true;
}
public int hashCode( )
{
int result = 17;
result = result * 31 + lastName.hashCode();
result = result * 31 + ssn.hashCode();
return result;
}
public String toString()
{
StringBuffer sb = new StringBuffer(getClass().getName());
sb.append("[lastName=");
sb.append(lastName);
sb.append(",ssn=");
sb.append(ssn);
sb.append(",deserialized=");
sb.append(deserialized);
sb.append("]");
return sb.toString();
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException
{
ois.defaultReadObject();
deserialized = true;
}
}

View File

@ -0,0 +1,111 @@
/*
* 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.jbc2.functional.classloader;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.jboss.cache.Fqn;
import org.jboss.cache.notifications.annotation.CacheListener;
import org.jboss.cache.notifications.annotation.NodeCreated;
import org.jboss.cache.notifications.annotation.NodeModified;
import org.jboss.cache.notifications.annotation.NodeVisited;
import org.jboss.cache.notifications.event.NodeCreatedEvent;
import org.jboss.cache.notifications.event.NodeModifiedEvent;
import org.jboss.cache.notifications.event.NodeVisitedEvent;
@CacheListener
public class CacheAccessListener
{
HashSet<Fqn<String>> modified = new HashSet<Fqn<String>>();
HashSet<Fqn<String>> accessed = new HashSet<Fqn<String>>();
public void clear()
{
modified.clear();
accessed.clear();
}
@NodeModified
public void nodeModified(NodeModifiedEvent event)
{
if (!event.isPre())
{
Fqn<String> fqn = event.getFqn();
System.out.println("MyListener - Modified node " + fqn.toString());
modified.add(fqn);
}
}
@NodeCreated
public void nodeCreated(NodeCreatedEvent event)
{
if (!event.isPre())
{
Fqn<String> fqn = event.getFqn();
System.out.println("MyListener - Created node " + fqn.toString());
modified.add(fqn);
}
}
@NodeVisited
public void nodeVisited(NodeVisitedEvent event)
{
if (!event.isPre())
{
Fqn<String> fqn = event.getFqn();
System.out.println("MyListener - Visited node " + fqn.toString());
accessed.add(fqn);
}
}
public boolean getSawRegionModification(String regionName)
{
return getSawRegion(regionName, modified);
}
public boolean getSawRegionAccess(String regionName)
{
return getSawRegion(regionName, accessed);
}
private boolean getSawRegion(String regionName, Set<Fqn<String>> sawEvent)
{
boolean saw = false;
Fqn<String> fqn = Fqn.fromString(regionName);
for (Iterator<Fqn<String>> it = sawEvent.iterator(); it.hasNext();)
{
Fqn<String> modified = (Fqn<String>) it.next();
if (modified.isChildOf(fqn))
{
it.remove();
saw = true;
}
}
return saw;
}
}

View File

@ -0,0 +1,285 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, 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.jbc2.functional.classloader;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import javax.transaction.TransactionManager;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Comment
*
* @author Brian Stansberry
*/
public class ClassLoaderTestDAO
{
private static final Logger log = LoggerFactory.getLogger(ClassLoaderTestDAO.class);
private SessionFactory sessionFactory;
private TransactionManager tm;
private Class acctClass;
private Class holderClass;
private Method setId;
private Method setBalance;
private Method setBranch;
private Method setHolder;
private Object smith;
private Object jones;
private Object barney;
private Method setName;
private Method setSsn;
public ClassLoaderTestDAO(SessionFactory factory, TransactionManager tm) throws Exception
{
this.sessionFactory = factory;
this.tm = tm;
acctClass = Thread.currentThread().getContextClassLoader().loadClass(getClass().getPackage().getName() + ".Account");
holderClass = Thread.currentThread().getContextClassLoader().loadClass(getClass().getPackage().getName() + ".AccountHolder");
setId = acctClass.getMethod("setId", Integer.class);
setBalance = acctClass.getMethod("setBalance", Integer.class);
setBranch = acctClass.getMethod("setBranch", String.class);
setHolder = acctClass.getMethod("setAccountHolder", holderClass);
setName = holderClass.getMethod("setLastName", String.class);
setSsn = holderClass.getMethod("setSsn", String.class);
smith = holderClass.newInstance();
setName.invoke(smith, "Smith");
setSsn.invoke(smith, "1000");
jones = holderClass.newInstance();
setName.invoke(jones, "Jones");
setSsn.invoke(jones, "2000");
barney = holderClass.newInstance();
setName.invoke(barney, "Barney");
setSsn.invoke(barney, "3000");
}
public Object getSmith() {
return smith;
}
public Object getJones() {
return jones;
}
public Object getBarney() {
return barney;
}
public void updateAccountBranch(Integer id, String branch) throws Exception
{
log.debug("Updating account " + id + " to branch " + branch);
tm.begin();
try {
Session session = sessionFactory.getCurrentSession();
Object account = session.get(acctClass, id);
setBranch.invoke(account, branch);
session.update(account);
tm.commit();
}
catch (Exception e) {
log.error("rolling back", e);
tm.rollback();
throw e;
}
log.debug("Updated account " + id + " to branch " + branch);
}
public int getCountForBranch(String branch, boolean useRegion) throws Exception
{
tm.begin();
try {
Query query = sessionFactory.getCurrentSession().createQuery("select account from Account as account where account.branch = :branch");
query.setString("branch", branch);
if (useRegion)
{
query.setCacheRegion("AccountRegion");
}
query.setCacheable(true);
int result = query.list().size();
tm.commit();
return result;
}
catch (Exception e) {
log.error("rolling back", e);
tm.rollback();
throw e;
}
}
public void createAccount(Object holder, Integer id, Integer openingBalance, String branch) throws Exception
{
log.debug("Creating account " + id);
tm.begin();
try {
Object account = acctClass.newInstance();
setId.invoke(account, id);
setHolder.invoke(account, holder);
setBalance.invoke(account, openingBalance);
setBranch.invoke(account, branch);
sessionFactory.getCurrentSession().persist(account);
tm.commit();
}
catch (Exception e) {
log.error("rolling back", e);
tm.rollback();
throw e;
}
log.debug("Created account " + id);
}
public void updateAccountBalance(Integer id, Integer newBalance) throws Exception
{
log.debug("Updating account " + id + " to balance " + newBalance);
tm.begin();
try {
Session session = sessionFactory.getCurrentSession();
Object account = session.get(acctClass, id);
setBalance.invoke(account, newBalance);
session.update(account);
tm.commit();
}
catch (Exception e) {
log.error("rolling back", e);
tm.rollback();
throw e;
}
log.debug("Updated account " + id + " to balance " + newBalance);
}
public String getBranch(Object holder, boolean useRegion) throws Exception
{
tm.begin();
try {
Query query = sessionFactory.getCurrentSession().createQuery("select account.branch from Account as account where account.accountHolder = ?");
query.setParameter(0, holder);
if (useRegion)
{
query.setCacheRegion("AccountRegion");
}
query.setCacheable(true);
String result = (String) query.list().get(0);
tm.commit();
return result;
}
catch (Exception e) {
log.error("rolling back", e);
tm.rollback();
throw e;
}
}
public int getTotalBalance(Object holder, boolean useRegion)
throws Exception
{
List results = null;
tm.begin();
try {
Query query = sessionFactory.getCurrentSession().createQuery("select account.balance from Account as account where account.accountHolder = ?");
query.setParameter(0, holder);
query.setCacheable(true);
results = query.list();
tm.commit();
}
catch (Exception e) {
log.error("rolling back", e);
tm.rollback();
throw e;
}
int total = 0;
if (results != null)
{
for (Iterator it = results.iterator(); it.hasNext();)
{
total += ((Integer) it.next()).intValue();
System.out.println("Total = " + total);
}
}
return total;
}
public void cleanup() throws Exception
{
internalCleanup();
}
private void internalCleanup() throws Exception
{
if (sessionFactory != null)
{
tm.begin();
try {
Session session = sessionFactory.getCurrentSession();
Query query = session.createQuery("select account from Account as account");
List accts = query.list();
if (accts != null)
{
for (Iterator it = accts.iterator(); it.hasNext();)
{
try
{
Object acct = it.next();
log.info("Removing " + acct);
session.delete(acct);
}
catch (Exception ignored) {}
}
}
tm.commit();
}
catch (Exception e) {
tm.rollback();
throw e;
}
}
}
public void remove()
{
try
{
internalCleanup();
}
catch (Exception e)
{
log.error("Caught exception in remove", e);
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved.
*
* 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, v. 2.1. This program is distributed in the
* hope that it will be useful, but WITHOUT A 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, v.2.1 along with this
* distribution; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Red Hat Author(s): Brian Stansberry
*/
package org.hibernate.test.cache.jbc2.functional.classloader;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.hibernate.test.cache.jbc2.functional.util.IsolatedCacheTestSetup;
/**
* Optimistic locking version of IsolatedClassLoaderTest.
*
* @author <a href="brian.stansberry@jboss.com">Brian Stansberry</a>
* @version $Revision: 1 $
*/
public class OptimisticIsolatedClassLoaderTest extends PessimisticIsolatedClassLoaderTest
{
private static final String CACHE_CONFIG = "optimistic-shared";
/**
* Create a new OptimisticIsolatedClassLoaderTest.
*
* @param name
*/
public OptimisticIsolatedClassLoaderTest(String name)
{
super(name);
}
public static Test suite() throws Exception {
TestSuite suite = new TestSuite(OptimisticIsolatedClassLoaderTest.class);
String[] acctClasses = { OUR_PACKAGE + ".Account", OUR_PACKAGE + ".AccountHolder" };
return new IsolatedCacheTestSetup(suite, acctClasses, CACHE_CONFIG);
}
protected String getEntityCacheConfigName() {
return CACHE_CONFIG;
}
}

View File

@ -0,0 +1,370 @@
/*
* 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.jbc2.functional.classloader;
import javax.transaction.TransactionManager;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.hibernate.SessionFactory;
import org.hibernate.cache.StandardQueryCache;
import org.hibernate.cache.jbc2.BasicRegionAdapter;
import org.hibernate.cache.jbc2.builder.MultiplexingCacheInstanceManager;
import org.hibernate.cache.jbc2.query.QueryResultsRegionImpl;
import org.hibernate.cfg.Configuration;
import org.hibernate.test.cache.jbc2.functional.DualNodeTestCaseBase;
import org.hibernate.test.cache.jbc2.functional.util.DualNodeJtaTransactionManagerImpl;
import org.hibernate.test.cache.jbc2.functional.util.DualNodeTestUtil;
import org.hibernate.test.cache.jbc2.functional.util.IsolatedCacheTestSetup;
import org.hibernate.test.cache.jbc2.functional.util.TestCacheInstanceManager;
import org.hibernate.test.cache.jbc2.functional.util.TestJBossCacheRegionFactory;
import org.jboss.cache.Cache;
import org.jboss.cache.CacheManager;
import org.jboss.cache.Fqn;
import org.jboss.cache.Region;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tests entity and query caching when class of objects being cached are not
* visible to JBoss Cache's classloader. Also serves as a general integration
* test.
* <p/>
* This test stores an object (AccountHolder) that isn't visible to the JBC
* 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())
*/
public class PessimisticIsolatedClassLoaderTest
extends DualNodeTestCaseBase
{
public static final String OUR_PACKAGE = PessimisticIsolatedClassLoaderTest.class.getPackage().getName();
private static final String CACHE_CONFIG = "pessimistic-shared";
protected static final long SLEEP_TIME = 300L;
protected final Logger log = LoggerFactory.getLogger(getClass());
static int test = 0;
public PessimisticIsolatedClassLoaderTest(String name)
{
super(name);
}
public static Test suite() throws Exception {
TestSuite suite = new TestSuite(PessimisticIsolatedClassLoaderTest.class);
String[] acctClasses = { OUR_PACKAGE + ".Account", OUR_PACKAGE + ".AccountHolder" };
return new IsolatedCacheTestSetup(suite, acctClasses, CACHE_CONFIG);
}
@Override
protected Class getCacheRegionFactory()
{
return TestJBossCacheRegionFactory.class;
}
@Override
protected boolean getUseQueryCache()
{
return true;
}
@Override
public String[] getMappings()
{
return new String[] { "cache/jbc2/functional/classloader/Account.hbm.xml" };
}
@Override
protected void configureCacheFactory(Configuration cfg)
{
cfg.setProperty(MultiplexingCacheInstanceManager.ENTITY_CACHE_RESOURCE_PROP,
getEntityCacheConfigName());
cfg.setProperty(MultiplexingCacheInstanceManager.TIMESTAMP_CACHE_RESOURCE_PROP,
getEntityCacheConfigName());
}
protected String getEntityCacheConfigName() {
return CACHE_CONFIG;
}
@Override
protected void cleanupTransactionManagement() {
// Don't clean up the managers, just the transactions
// Managers are still needed by the long-lived caches
DualNodeJtaTransactionManagerImpl.cleanupTransactions();
}
/**
* Simply confirms that the test fixture's classloader isolation setup
* is functioning as expected.
*
* @throws Exception
*/
public void testIsolatedSetup() throws Exception
{
// Bind a listener to the "local" cache
// Our region factory makes its CacheManager available to us
org.jboss.cache.CacheManager localManager = TestCacheInstanceManager.getTestCacheManager(DualNodeTestUtil.LOCAL);
org.jboss.cache.Cache localCache = localManager.getCache(getEntityCacheConfigName(), true);
// Bind a listener to the "remote" cache
org.jboss.cache.CacheManager remoteManager = TestCacheInstanceManager.getTestCacheManager(DualNodeTestUtil.REMOTE);
org.jboss.cache.Cache remoteCache = remoteManager.getCache(getEntityCacheConfigName(), true);
ClassLoader cl = Thread.currentThread().getContextClassLoader();
log.info("TCCL is " + cl);
Thread.currentThread().setContextClassLoader(cl.getParent());
org.jboss.cache.Fqn fqn = org.jboss.cache.Fqn.fromString("/isolated1");
org.jboss.cache.Region r = localCache.getRegion(fqn, true);
r.activate();
r = remoteCache.getRegion(fqn, true);
r.activate();
Thread.currentThread().setContextClassLoader(cl);
Account acct = new Account();
acct.setAccountHolder(new AccountHolder());
try
{
localCache.put(fqn, "key", acct);
fail("Should not have succeeded in putting acct -- classloader not isolated");
}
catch (Exception e) {
log.info("Caught exception as desired", e);
}
localCache.getRegion(fqn, false).registerContextClassLoader(Thread.currentThread().getContextClassLoader());
remoteCache.getRegion(fqn, false).registerContextClassLoader(Thread.currentThread().getContextClassLoader());
localCache.put(fqn, "key", acct);
assertEquals(acct.getClass().getName(), remoteCache.get(fqn, "key").getClass().getName());
}
public void testClassLoaderHandlingNamedQueryRegion() throws Exception {
queryTest(true);
}
public void testClassLoaderHandlingStandardQueryCache() throws Exception {
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
CacheManager localManager = TestCacheInstanceManager.getTestCacheManager(DualNodeTestUtil.LOCAL);
Cache localCache = localManager.getCache(getEntityCacheConfigName(), true);
CacheAccessListener localListener = new CacheAccessListener();
localCache.addCacheListener(localListener);
TransactionManager localTM = localCache.getConfiguration().getRuntimeConfig().getTransactionManager();
// Bind a listener to the "remote" cache
CacheManager remoteManager = TestCacheInstanceManager.getTestCacheManager(DualNodeTestUtil.REMOTE);
Cache remoteCache = remoteManager.getCache(getEntityCacheConfigName(), true);
CacheAccessListener remoteListener = new CacheAccessListener();
remoteCache.addCacheListener(remoteListener);
TransactionManager remoteTM = remoteCache.getConfiguration().getRuntimeConfig().getTransactionManager();
SessionFactory localFactory = getEnvironment().getSessionFactory();
SessionFactory remoteFactory = getSecondNodeEnvironment().getSessionFactory();
ClassLoaderTestDAO dao0 = new ClassLoaderTestDAO(localFactory, localTM);
ClassLoaderTestDAO dao1 = new ClassLoaderTestDAO(remoteFactory, remoteTM);
// Determine whether our query region is already there (in which case it
// will receive remote messages immediately) or is yet to be created on
// first use (in which case it will initially discard remote messages)
String regionName = createRegionName(useNamedRegion ? "AccountRegion" : StandardQueryCache.class.getName());
Region queryRegion = remoteCache.getRegion(Fqn.fromString(regionName), false);
boolean queryRegionExists = queryRegion != null && queryRegion.isActive();
// Initial ops on node 0
setupEntities(dao0);
// Query on post code count
assertEquals("63088 has correct # of accounts", 6, dao0.getCountForBranch("63088", useNamedRegion));
assertTrue("Query cache used " + regionName,
localListener.getSawRegionModification(regionName));
// Clear the access state
localListener.getSawRegionAccess(regionName);
log.info("First query on node0 done");
// Sleep a bit to allow async repl to happen
sleep(SLEEP_TIME);
// If region isn't activated yet, should not have been modified
if (!queryRegionExists)
{
assertFalse("Query cache remotely modified " + regionName,
remoteListener.getSawRegionModification(regionName));
// Clear the access state
remoteListener.getSawRegionAccess(regionName);
}
else
{
assertTrue("Query cache remotely modified " + regionName,
remoteListener.getSawRegionModification(regionName));
// Clear the access state
remoteListener.getSawRegionAccess(regionName);
}
// Do query again from node 1
assertEquals("63088 has correct # of accounts", 6, dao1.getCountForBranch("63088", useNamedRegion));
if (!queryRegionExists)
{
// Query should have activated the region and then been inserted
assertTrue("Query cache modified " + regionName,
remoteListener.getSawRegionModification(regionName));
// Clear the access state
remoteListener.getSawRegionAccess(regionName);
}
log.info("First query on node 1 done");
// We now have the query cache region activated on both nodes.
// Sleep a bit to allow async repl to happen
sleep(SLEEP_TIME);
// Do some more queries on node 0
assertEquals("Correct branch for Smith", "94536", dao0.getBranch(dao0.getSmith(), useNamedRegion));
assertEquals("Correct high balances for Jones", 40, dao0.getTotalBalance(dao0.getJones(), useNamedRegion));
assertTrue("Query cache used " + regionName,
localListener.getSawRegionModification(regionName));
// Clear the access state
localListener.getSawRegionAccess(regionName);
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
assertTrue("Query cache remotely modified " + regionName,
remoteListener.getSawRegionModification(regionName));
// Clear the access state
remoteListener.getSawRegionAccess(regionName);
// Do queries again from node 1
assertEquals("Correct branch for Smith", "94536", dao1.getBranch(dao1.getSmith(), useNamedRegion));
assertEquals("Correct high balances for Jones", 40, dao1.getTotalBalance(dao1.getJones(), useNamedRegion));
// Should be no change; query was already there
assertFalse("Query cache modified " + regionName,
remoteListener.getSawRegionModification(regionName));
assertTrue("Query cache accessed " + regionName,
remoteListener.getSawRegionAccess(regionName));
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 String createRegionName(String noPrefix)
{
String combined = getRegionPrefix() == null ? noPrefix : getRegionPrefix() + '.' + noPrefix;
return BasicRegionAdapter.getTypeLastRegionFqn(combined, getRegionPrefix(), QueryResultsRegionImpl.TYPE).toString();
}
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 = createRegionName(StandardQueryCache.class.getName());
String acctName = createRegionName("AccountRegion");
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");
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved.
*
* 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, v. 2.1. This program is distributed in the
* hope that it will be useful, but WITHOUT A 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, v.2.1 along with this
* distribution; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Red Hat Author(s): Brian Stansberry
*/
package org.hibernate.test.cache.jbc2.functional.util;
import org.jboss.cache.CacheImpl;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.config.Configuration;
/**
* CacheFactory impl that allows us to register a desired default classloader
* for deserializing RPCs.
*
* @author <a href="brian.stansberry@jboss.com">Brian Stansberry</a>
*/
public class CustomClassLoaderCacheFactory<K, V> extends DefaultCacheFactory<K, V>
{
private ClassLoader customClassLoader;
/**
* Create a new CustomClassLoaderCacheFactory.
*/
public CustomClassLoaderCacheFactory(ClassLoader customClassLoader)
{
super();
this.customClassLoader = customClassLoader;
}
@Override
protected void bootstrap(CacheImpl cache, CacheSPI spi, Configuration configuration)
{
super.bootstrap(cache, spi, configuration);
// Replace the deployerClassLoader
componentRegistry.registerComponent("deployerClassLoader", customClassLoader, ClassLoader.class);
}
}

View File

@ -115,9 +115,11 @@ public class DualNodeJtaTransactionImpl implements Transaction {
}
}
for ( int i = 0; i < synchronizations.size(); i++ ) {
Synchronization s = ( Synchronization ) synchronizations.get( i );
s.afterCompletion( status );
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;

View File

@ -35,6 +35,11 @@ import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.hibernate.test.cache.jbc2.functional.classloader.ClassLoaderTestDAO;
import org.hsqldb.lib.Iterator;
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.
@ -42,18 +47,44 @@ import javax.transaction.TransactionManager;
* @author Brian Stansberry
*/
public class DualNodeJtaTransactionManagerImpl implements TransactionManager {
private static final Hashtable INSTANCES = new Hashtable();
private static final Logger log = LoggerFactory.getLogger(DualNodeJtaTransactionManagerImpl.class);
private static final Hashtable INSTANCES = new Hashtable();
private DualNodeJtaTransactionImpl currentTransaction;
private String nodeId;
public synchronized static DualNodeJtaTransactionManagerImpl getInstance(String nodeId) {
DualNodeJtaTransactionManagerImpl tm = (DualNodeJtaTransactionManagerImpl) INSTANCES.get(nodeId);
if (tm == null) {
tm = new DualNodeJtaTransactionManagerImpl();
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 {
return currentTransaction == null ? Status.STATUS_NO_TRANSACTION : currentTransaction.getStatus();
@ -72,7 +103,8 @@ public class DualNodeJtaTransactionManagerImpl implements TransactionManager {
}
public Transaction suspend() throws SystemException {
DualNodeJtaTransactionImpl suspended = currentTransaction;
log.trace(nodeId + ": Suspending " + currentTransaction + " for thread " + Thread.currentThread().getName());
DualNodeJtaTransactionImpl suspended = currentTransaction;
currentTransaction = null;
return suspended;
}
@ -80,6 +112,7 @@ public class DualNodeJtaTransactionManagerImpl implements TransactionManager {
public void resume(Transaction transaction)
throws InvalidTransactionException, IllegalStateException, SystemException {
currentTransaction = ( DualNodeJtaTransactionImpl ) transaction;
log.trace(nodeId + ": Resumed " + currentTransaction + " for thread " + Thread.currentThread().getName());
}
public void commit()
@ -112,4 +145,12 @@ public class DualNodeJtaTransactionManagerImpl implements TransactionManager {
currentTransaction = null;
}
}
public String toString() {
StringBuffer sb = new StringBuffer(getClass().getName());
sb.append("[nodeId=");
sb.append(nodeId);
sb.append("]");
return sb.toString();
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved.
*
* 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, v. 2.1. This program is distributed in the
* hope that it will be useful, but WITHOUT A 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, v.2.1 along with this
* distribution; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Red Hat Author(s): Brian Stansberry
*/
package org.hibernate.test.cache.jbc2.functional.util;
import junit.framework.Test;
import org.hibernate.test.util.SelectedClassnameClassLoader;
import org.hibernate.test.util.SelectedClassnameClassLoaderTestSetup;
import org.jboss.cache.Cache;
import org.jboss.cache.config.Configuration;
/**
* A TestSetup that uses SelectedClassnameClassLoader to ensure that
* certain classes are not visible to JBoss Cache or JGroups' classloader.
*
* @author <a href="brian.stansberry@jboss.com">Brian Stansberry</a>
* @version $Revision: 1 $
*/
public class IsolatedCacheTestSetup extends SelectedClassnameClassLoaderTestSetup
{
public static final String DEF_CACHE_FACTORY_RESOURCE = "org/hibernate/cache/jbc2/builder/jbc2-configs.xml";
public static final String DEF_JGROUPS_RESOURCE = "org/hibernate/cache/jbc2/builder/jgroups-stacks.xml";
private String[] isolatedClasses;
private String cacheConfig;
/**
* Create a new IsolatedCacheTestSetup.
*/
public IsolatedCacheTestSetup(Test test,
String[] isolatedClasses,
String cacheConfig)
{
super(test, null, null, isolatedClasses);
this.isolatedClasses = isolatedClasses;
this.cacheConfig = cacheConfig;
}
@Override
protected void setUp() throws Exception
{
super.setUp();
// At this point the TCCL cannot see the isolatedClasses
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
org.jgroups.ChannelFactory cf = new org.jgroups.JChannelFactory();
cf.setMultiplexerConfig(DEF_JGROUPS_RESOURCE);
org.jboss.cache.CacheManagerImpl cm = new org.jboss.cache.CacheManagerImpl(DEF_CACHE_FACTORY_RESOURCE, cf);
cm.start();
TestCacheInstanceManager.addTestCacheManager(DualNodeTestUtil.LOCAL, cm);
// Inject the desired defaultClassLoader into our caches
Configuration cfg = cm.getConfigurationRegistry().getConfiguration(cacheConfig);
Cache cache = new CustomClassLoaderCacheFactory(tccl).createCache(cfg, false);
cache.getConfiguration().getRuntimeConfig().setMuxChannelFactory(cm.getChannelFactory());
cm.registerCache(cache, cacheConfig);
cf = new org.jgroups.JChannelFactory();
cf.setMultiplexerConfig(DEF_JGROUPS_RESOURCE);
cm = new org.jboss.cache.CacheManagerImpl(DEF_CACHE_FACTORY_RESOURCE, cf);
cm.start();
TestCacheInstanceManager.addTestCacheManager(DualNodeTestUtil.REMOTE, cm);
cfg = cm.getConfigurationRegistry().getConfiguration(cacheConfig);
cache = new CustomClassLoaderCacheFactory(tccl).createCache(cfg, false);
cache.getConfiguration().getRuntimeConfig().setMuxChannelFactory(cm.getChannelFactory());
cm.registerCache(cache, cacheConfig);
// Now make the isolatedClasses visible
SelectedClassnameClassLoader visible = new SelectedClassnameClassLoader(isolatedClasses, null, null, tccl);
Thread.currentThread().setContextClassLoader(visible);
}
@Override
protected void tearDown() throws Exception
{
try {
super.tearDown();
}
finally {
TestCacheInstanceManager.clearCacheManagers();
DualNodeJtaTransactionManagerImpl.cleanupTransactions();
DualNodeJtaTransactionManagerImpl.cleanupTransactionManagers();
}
}
}

View File

@ -30,6 +30,9 @@ import org.hibernate.cache.CacheException;
import org.hibernate.cache.jbc2.builder.MultiplexingCacheInstanceManager;
import org.hibernate.cfg.Settings;
import org.jboss.cache.CacheManager;
import org.jboss.cache.CacheManagerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
@ -40,6 +43,8 @@ import org.jboss.cache.CacheManager;
* @author <a href="brian.stansberry@jboss.com">Brian Stansberry</a>
*/
public class TestCacheInstanceManager extends MultiplexingCacheInstanceManager {
private static final Logger log = LoggerFactory.getLogger(TestCacheInstanceManager.class);
private static final Hashtable cacheManagers = new Hashtable();
@ -47,7 +52,28 @@ public class TestCacheInstanceManager extends MultiplexingCacheInstanceManager {
return (CacheManager) cacheManagers.get(name);
}
public static void addTestCacheManager(String name,CacheManager manager) {
cacheManagers.put(name, manager);
}
public static void clearCacheManagers() {
for (java.util.Iterator it = cacheManagers.values().iterator(); it.hasNext();) {
CacheManager cm = (CacheManager) it.next();
try
{
if (cm instanceof CacheManagerImpl)
((CacheManagerImpl) cm).stop();
}
catch (Exception e)
{
log.error("Exception cleaning up CacheManager " + cm);
}
}
cacheManagers.clear();
}
private String cacheManagerName;
private boolean locallyAdded;
/**
* Create a new TestCacheInstanceManager.
@ -58,17 +84,26 @@ public class TestCacheInstanceManager extends MultiplexingCacheInstanceManager {
@Override
public void start(Settings settings, Properties properties) throws CacheException {
cacheManagerName = properties.getProperty(DualNodeTestUtil.NODE_ID_PROP);
CacheManager existing = getTestCacheManager(cacheManagerName);
locallyAdded = (existing == null);
if (!locallyAdded) {
setCacheFactory(existing);
}
super.start(settings, properties);
cacheManagerName = properties.getProperty(DualNodeTestUtil.NODE_ID_PROP);
cacheManagers.put(cacheManagerName, getCacheFactory());
if (locallyAdded)
cacheManagers.put(cacheManagerName, getCacheFactory());
}
@Override
public void stop()
{
cacheManagers.remove(cacheManagerName);
if (locallyAdded)
cacheManagers.remove(cacheManagerName);
super.stop();
}

View File

@ -0,0 +1,288 @@
/*
* 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.util;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A ClassLoader that loads classes whose classname begins with one of a
* given set of strings, without attempting first to delegate to its
* parent loader.
* <p>
* This class is intended to allow emulation of 2 different types of common J2EE
* classloading situations.
* <ul>
* <li>Servlet-style child-first classloading, where this class is the
* child loader.</li>
* <li>Parent-first classloading where the parent does not have access to
* certain classes</li>
* </ul>
* </p>
* <p>
* This class can also be configured to raise a ClassNotFoundException if
* asked to load certain classes, thus allowing classes on the classpath
* to be hidden from a test environment.
* </p>
*
* @author Brian Stansberry
*/
public class SelectedClassnameClassLoader extends ClassLoader
{
private Logger log = LoggerFactory.getLogger(SelectedClassnameClassLoader.class);
private String[] includedClasses = null;
private String[] excludedClasses = null;
private String[] notFoundClasses = null;
private Map<String, Class> classes = new java.util.HashMap<String, Class>();
/**
* Creates a new classloader that loads the given classes.
*
* @param includedClasses array of class or package names that should be
* directly loaded by this loader. Classes
* whose name starts with any of the strings
* in this array will be loaded by this class,
* unless their name appears in
* <code>excludedClasses</code>.
* Can be <code>null</code>
* @param excludedClasses array of class or package names that should NOT
* be directly loaded by this loader. Loading of
* classes whose name starts with any of the
* strings in this array will be delegated to
* <code>parent</code>, even if the classes
* package or classname appears in
* <code>includedClasses</code>. Typically this
* parameter is used to exclude loading one or
* more classes in a package whose other classes
* are loaded by this object.
* @param parent ClassLoader to which loading of classes should
* be delegated if necessary
*/
public SelectedClassnameClassLoader(String[] includedClasses,
String[] excludedClasses,
ClassLoader parent)
{
this(includedClasses, excludedClasses, null, parent);
}
/**
* Creates a new classloader that loads the given classes.
*
* @param includedClasses array of class or package names that should be
* directly loaded by this loader. Classes
* whose name starts with any of the strings
* in this array will be loaded by this class,
* unless their name appears in
* <code>excludedClasses</code>.
* Can be <code>null</code>
* @param excludedClasses array of class or package names that should NOT
* be directly loaded by this loader. Loading of
* classes whose name starts with any of the
* strings in this array will be delegated to
* <code>parent</code>, even if the classes
* package or classname appears in
* <code>includedClasses</code>. Typically this
* parameter is used to exclude loading one or
* more classes in a package whose other classes
* are loaded by this object.
* @param notFoundClasses array of class or package names for which this
* should raise a ClassNotFoundException
* @param parent ClassLoader to which loading of classes should
* be delegated if necessary
*/
public SelectedClassnameClassLoader(String[] includedClasses,
String[] excludedClasses,
String[] notFoundClasses,
ClassLoader parent)
{
super(parent);
this.includedClasses = includedClasses;
this.excludedClasses = excludedClasses;
this.notFoundClasses = notFoundClasses;
log.debug("created " + this);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
log.trace("loadClass(" + name + "," + resolve + ")");
if (isIncluded(name) && (isExcluded(name) == false))
{
Class c = findClass(name);
if (resolve)
{
resolveClass(c);
}
return c;
}
else if (isNotFound(name))
{
throw new ClassNotFoundException(name + " is discarded");
}
else
{
return super.loadClass(name, resolve);
}
}
protected Class<?> findClass(String name) throws ClassNotFoundException
{
log.trace("findClass(" + name + ")");
Class result = classes.get(name);
if (result != null)
{
return result;
}
if (isIncluded(name) && (isExcluded(name) == false))
{
result = createClass(name);
}
else if (isNotFound(name))
{
throw new ClassNotFoundException(name + " is discarded");
}
else
{
result = super.findClass(name);
}
classes.put(name, result);
return result;
}
protected Class createClass(String name) throws ClassFormatError, ClassNotFoundException
{
log.info("createClass(" + name + ")");
try
{
InputStream is = getResourceAsStream(name.replace('.', '/').concat(".class"));
byte[] bytes = new byte[1024];
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
int read;
while ((read = is.read(bytes)) > -1)
{
baos.write(bytes, 0, read);
}
bytes = baos.toByteArray();
return this.defineClass(name, bytes, 0, bytes.length);
}
catch (FileNotFoundException e)
{
throw new ClassNotFoundException("cannot find " + name, e);
}
catch (IOException e)
{
throw new ClassNotFoundException("cannot read " + name, e);
}
}
protected boolean isIncluded(String className)
{
if (includedClasses != null)
{
for (int i = 0; i < includedClasses.length; i++)
{
if (className.startsWith(includedClasses[i]))
{
return true;
}
}
}
return false;
}
protected boolean isExcluded(String className)
{
if (excludedClasses != null)
{
for (int i = 0; i < excludedClasses.length; i++)
{
if (className.startsWith(excludedClasses[i]))
{
return true;
}
}
}
return false;
}
protected boolean isNotFound(String className)
{
if (notFoundClasses != null)
{
for (int i = 0; i < notFoundClasses.length; i++)
{
if (className.startsWith(notFoundClasses[i]))
{
return true;
}
}
}
return false;
}
public String toString() {
String s = getClass().getName();
s += "[includedClasses=";
s += listClasses(includedClasses);
s += ";excludedClasses=";
s += listClasses(excludedClasses);
s += ";notFoundClasses=";
s += listClasses(notFoundClasses);
s += ";parent=";
s += getParent();
s += "]";
return s;
}
private static String listClasses(String[] classes) {
if (classes == null) return null;
String s = "";
for (int i = 0; i < classes.length; i++) {
if (i > 0)
s += ",";
s += classes[i];
}
return s;
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved.
*
* 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, v. 2.1. This program is distributed in the
* hope that it will be useful, but WITHOUT A 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, v.2.1 along with this
* distribution; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Red Hat Author(s): Brian Stansberry
*/
package org.hibernate.test.util;
import junit.extensions.TestSetup;
import junit.framework.Test;
/**
* A TestSetup that makes SelectedClassnameClassLoader the thread
* context classloader for the duration of the test.
*
* @author <a href="brian.stansberry@jboss.com">Brian Stansberry</a>
* @version $Revision: 1 $
*/
public class SelectedClassnameClassLoaderTestSetup extends TestSetup
{
private ClassLoader originalTCCL;
private String[] includedClasses;
private String[] excludedClasses;
private String[] notFoundClasses;
/**
* Create a new SelectedClassnameClassLoaderTestSetup.
*
* @param test
*/
public SelectedClassnameClassLoaderTestSetup(Test test,
String[] includedClasses,
String[] excludedClasses,
String[] notFoundClasses)
{
super(test);
this.includedClasses = includedClasses;
this.excludedClasses = excludedClasses;
this.notFoundClasses = notFoundClasses;
}
@Override
protected void setUp() throws Exception
{
super.setUp();
originalTCCL = Thread.currentThread().getContextClassLoader();
ClassLoader parent = originalTCCL == null ? getClass().getClassLoader() : originalTCCL;
ClassLoader selectedTCCL = new SelectedClassnameClassLoader(includedClasses, excludedClasses, notFoundClasses, parent);
Thread.currentThread().setContextClassLoader(selectedTCCL);
}
@Override
protected void tearDown() throws Exception
{
Thread.currentThread().setContextClassLoader(originalTCCL);
super.tearDown();
}
}