/*
* JBoss, Home of Professional Open Source
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.cache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.ConfigurationException;
import org.jboss.cache.config.parsing.XmlConfigurationParser;
import org.jboss.cache.config.parsing.XmlConfigurationParser2x;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import org.jboss.cache.util.TestingUtil;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author <a href="mailto:dpospisi@redhat.com">Dominik Pospisil (dpospisi@redhat.com)</a>
*/
public class UnitTestCacheFactory<K, V> implements CacheFactory<K, V>
{
private final Log log = LogFactory.getLog(UnitTestCacheFactory.class);
/**
* Holds unique mcast_addr for each thread used for JGroups channel construction.
*/
private static final ThreadLocal<String> threadMcastIP = new ThreadLocal<String>()
{
private final AtomicInteger uniqueAddr = new AtomicInteger(11);
@Override
protected String initialValue()
{
return "228.10.10." + uniqueAddr.getAndIncrement();
}
};
/**
* Holds unique mcast_port for each thread used for JGroups channel construction.
*/
private static final ThreadLocal<Integer> threadMcastPort = new ThreadLocal<Integer>()
{
private final AtomicInteger uniquePort = new AtomicInteger(45589);
@Override
protected Integer initialValue()
{
return uniquePort.getAndIncrement();
}
};
/**
* For each thread holds list of caches created using this factory.
*/
private static final ThreadLocal<List<Cache>> threadCaches =
new ThreadLocal<List<Cache>>()
{
@Override
protected List<Cache> initialValue()
{
return new ArrayList<Cache>();
}
};
private final static List<Cache> allCaches = new ArrayList<Cache>();
/**
* For each thread holds the name of the test class which executed createCache factory method.
*/
private static final ThreadLocal<String> threadTestName = new ThreadLocal<String>();
// factory methods
public Cache<K, V> createCache() throws ConfigurationException
{
return createCache(true);
}
public Cache<K, V> createCache(boolean start) throws ConfigurationException
{
return createCache(new Configuration(), start);
}
public Cache<K, V> createCache(String configFileName) throws ConfigurationException
{
return createCache(configFileName, true);
}
public Cache<K, V> createCache(String configFileName, boolean start) throws ConfigurationException
{
XmlConfigurationParser parser = new XmlConfigurationParser();
Configuration c;
try
{
c = parser.parseFile(configFileName);
}
catch (ConfigurationException e)
{
System.out.println("Detected legacy configuration file format when parsing [" + configFileName + "]. Migrating to the new (3.x) file format is recommended. See FAQs for details.");
XmlConfigurationParser2x oldParser = new XmlConfigurationParser2x();
c = oldParser.parseFile(configFileName);
}
return createCache(c, start);
}
public Cache<K, V> createCache(Configuration configuration) throws ConfigurationException
{
return createCache(configuration, true);
}
public Cache<K, V> createCache(InputStream is) throws ConfigurationException
{
return createCache(is, true);
}
public Cache<K, V> createCache(InputStream is, boolean start) throws ConfigurationException
{
XmlConfigurationParser parser = new XmlConfigurationParser();
Configuration c = parser.parseStream(is);
return createCache(c, start);
}
public Cache<K, V> createCache(Configuration configuration, boolean start) throws ConfigurationException
{
// - Do not print out this exception - since tests are ALSO run from IDEs on the main thread. - MANIK
// if (!Thread.currentThread().getName().contains("pool"))
// {
// System.out.println("CreateCache called from wrong thread: " + Thread.currentThread().getName());
// }
checkCaches();
switch (configuration.getCacheMode())
{
case LOCAL:
// local cache, no channel used
break;
case REPL_SYNC:
case REPL_ASYNC:
case INVALIDATION_ASYNC:
case INVALIDATION_SYNC:
// replicated cache, update channel setup
mangleConfiguration(configuration);
break;
default:
log.info("Unknown cache mode!");
}
Cache<K, V> cache = new DefaultCacheFactory<K, V>().createCache(configuration, start);
List<Cache> caches = threadCaches.get();
caches.add(cache);
synchronized (allCaches)
{
allCaches.add(cache);
}
return cache;
}
/**
* Destroys all caches created by this factory in the current thread.
*
* @return true if some cleanup was actually performed
*/
public boolean cleanUp()
{
List<Cache> caches = new ArrayList<Cache>(threadCaches.get());
boolean ret = false;
for (Cache cache : caches)
{
TestingUtil.killCaches(cache);
ret = true;
}
return ret;
}
public void removeCache(Cache c)
{
// - Do not print out this exception - since tests are ALSO run from IDEs on the main thread. - MANIK
// if (!Thread.currentThread().getName().contains("pool"))
// {
// System.out.println("RemoveCache called from wrong thread.");
// }
List<Cache> caches = threadCaches.get();
synchronized (allCaches)
{
if (caches.contains(c))
{
caches.remove(c);
allCaches.remove(c);
}
else if (allCaches.contains(c))
{
System.out.println("[" + Thread.currentThread().getName() + "] WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Remove cache called from different thread.");
Thread.dumpStack();
}
}
}
/**
* Updates cluster configuration to ensure mutual thread isolation.
*
* @param configuration Configuration to update.
*/
public void mangleConfiguration(Configuration configuration)
{
configuration.setClusterConfig(mangleClusterConfiguration(configuration.getClusterConfig()));
// Check if the cluster name contains thread id. If not, append.
// We can not just append the threadId, since some of the tests are crating instances
// using configurations derived from configurations returned by this factory.
String clusterName = configuration.getClusterName();
// append thread id
if (clusterName.indexOf(Thread.currentThread().getName()) == -1)
{
clusterName = clusterName + "-" + Thread.currentThread().getName();
// System.out.println(getThreadId() + " Setting cluster name " + newClusterName);
}
// String testName = extractTestName();
// prepend test name
/*
if (clusterName.indexOf(testName) == -1) {
clusterName = testName + "-" + clusterName;
}
*/
configuration.setClusterName(clusterName);
}
/**
* Updates cluster configuration to ensure mutual thread isolation.
*/
public String mangleClusterConfiguration(String clusterConfig)
{
if (clusterConfig == null)
{
// No explicit cluster configuration found. we need to resolve the default config
// now in orded to be able to update it before the cache (and the channel) starts.
// TODO: this does not seem to be the best solution :(
clusterConfig = UnitTestCacheConfigurationFactory.getClusterConfigFromFile(
new Configuration().getDefaultClusterConfig());
}
// - Do not print out this exception - since tests are ALSO run from IDEs on the main thread. - MANIK
//
// if (Thread.currentThread().getName().equals("main")) {
// Exception e = new Exception("Invoked from main thread.");
// e.printStackTrace();
// };
// replace mcast_addr
Pattern pattern = Pattern.compile("mcast_addr=[^;]*");
Matcher m = pattern.matcher(clusterConfig);
if (m.find())
{
String origAddr = m.group().substring(m.group().indexOf("=") + 1);
String newAddr = threadMcastIP.get();
// System.out.println(getThreadId() + " Replacing mcast_addr " + origAddr + " with " + newAddr);
clusterConfig = m.replaceFirst("mcast_addr=" + newAddr);
}
else
{
Thread.dumpStack();
System.exit(1);
}
// replace mcast_port
pattern = Pattern.compile("mcast_port=[^;]*");
m = pattern.matcher(clusterConfig);
if (m.find())
{
// String origPort = m.group().substring(m.group().indexOf("=") + 1);
String newPort = threadMcastPort.get().toString();
// System.out.println(getThreadId() + " Replacing mcast_port " + origPort + " with " + newPort);
clusterConfig = m.replaceFirst("mcast_port=" + newPort);
}
return clusterConfig;
}
// private String getThreadId()
// {
// return "[" + Thread.currentThread().getName() + "]";
// }
private void checkCaches()
{
String lastTestName = threadTestName.get();
String currentTestName = extractTestName();
if ((lastTestName != null) && (!lastTestName.equals(currentTestName)))
{
String threadId = "[" + Thread.currentThread().getName() + "] ";
// we are running new test class
// check if there is a cache(s) instance left & kill it if possitive
if (cleanUp())
{
System.out.print(threadId + "WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ");
System.out.print(threadId + " A test method in " + lastTestName + " did not clean all cache instances properly. ");
System.out.println(threadId + " Use UnitTestCacheFactory.cleanUp() or TestngUtil.killCaches(...) ");
}
}
threadTestName.set(currentTestName);
}
private String extractTestName()
{
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
if (stack.length == 0) return null;
for (int i = stack.length - 1; i > 0; i--)
{
StackTraceElement e = stack[i];
String className = e.getClassName();
if (className.indexOf("org.jboss.cache") != -1) return className; //+ "." + e.getMethodName();
}
return null;
}
}