/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.cache.loader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.Fqn;
import org.jboss.cache.NodeSPI;
import org.jboss.cache.config.CacheLoaderConfig;
import org.jboss.cache.config.CacheLoaderConfig.IndividualCacheLoaderConfig;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.factories.UnitTestConfigurationFactory;
import org.jboss.cache.util.internals.ViewChangeListener;
import static org.testng.AssertJUnit.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.jboss.cache.UnitTestCacheFactory;
import org.jboss.cache.loader.testloaders.DummyInMemoryCacheLoader;
import org.jboss.cache.util.TestingUtil;
/**
* Unit test class for SingletonStoreCacheLoader
*
* @author <a href="mailto:galder.zamarreno@jboss.com">Galder Zamarreno</a>
*/
@Test(groups = "functional", sequential = true, testName = "loader.SingletonStoreCacheLoaderTest")
public class SingletonStoreCacheLoaderTest
{
private static final Log log = LogFactory.getLog(SingletonStoreCacheLoaderTest.class);
private CacheSPI<Object, Object> cache1, cache2, cache3;
@BeforeMethod(alwaysRun = true)
public void setUp() throws Exception
{
cache1 = (CacheSPI<Object, Object>) new UnitTestCacheFactory<Object, Object>().createCache(UnitTestConfigurationFactory.createConfiguration(CacheMode.REPL_SYNC), false, getClass());
cache2 = (CacheSPI<Object, Object>) new UnitTestCacheFactory<Object, Object>().createCache(UnitTestConfigurationFactory.createConfiguration(CacheMode.REPL_SYNC), false, getClass());
cache3 = (CacheSPI<Object, Object>) new UnitTestCacheFactory<Object, Object>().createCache(UnitTestConfigurationFactory.createConfiguration(CacheMode.REPL_SYNC), false, getClass());
}
public void testPutCacheLoaderWithNoPush() throws Exception
{
initSingletonNonPushCache(cache1);
initSingletonNonPushCache(cache2);
initSingletonNonPushCache(cache3);
createCaches();
statCaches();
cache1.put(fqn("/test1"), "key", "value");
cache2.put(fqn("/test2"), "key", "value");
cache3.put(fqn("/test3"), "key", "value");
CacheLoader cl1 = getDelegatingCacheLoader(cache1);
CacheLoader cl2 = getDelegatingCacheLoader(cache2);
CacheLoader cl3 = getDelegatingCacheLoader(cache3);
assertTrue("/test1 should have been entered in cl1", cl1.exists(fqn("/test1")));
assertTrue("/test2 should have been entered in cl1", cl1.exists(fqn("/test2")));
assertTrue("/test3 should have been entered in cl1", cl1.exists(fqn("/test3")));
stopCache1(true);
cache2.put(fqn("/test4"), "key", "value");
cache3.put(fqn("/test5"), "key", "value");
assertTrue("/test4 should have been entered in cl2", cl2.exists(fqn("/test4")));
assertTrue("/test5 should have been entered in cl2", cl2.exists(fqn("/test5")));
stopCache2(true);
cache3.put(fqn("/test6"), "key", "value");
assertTrue("/test5 should have been entered in cl3", cl3.exists(Fqn.fromString("/test6")));
}
public void testPutCacheLoaderWithPush() throws Exception
{
initSingletonWithPushCache(cache1);
initSingletonWithWaitingPushCache(cache2);
initSingletonWithPushCache(cache3);
createCaches();
statCaches();
cache1.put(fqn("/a"), "a-key", "a-value");
cache1.put(fqn("/a"), "aa-key", "aa-value");
cache1.put(fqn("/a/b"), "b-key", "b-value");
cache1.put(fqn("/a/b"), "bb-key", "bb-value");
cache1.put(fqn("/a/b/c"), "c-key", "c-value");
cache1.put(fqn("/a/b/d"), "d-key", "d-value");
cache1.put(fqn("/e"), "e-key", "e-value");
cache1.put(fqn("/e/f/g"), "g-key", "g-value");
CacheLoader cl1 = getDelegatingCacheLoader(cache1);
CacheLoader cl2 = getDelegatingCacheLoader(cache2);
CacheLoader cl3 = getDelegatingCacheLoader(cache3);
assertTrue(cl1.get(fqn("/a")).containsKey("a-key"));
assertTrue(cl1.get(fqn("/a")).containsKey("aa-key"));
assertTrue(cl1.get(fqn("/a/b")).containsKey("b-key"));
assertTrue(cl1.get(fqn("/a/b")).containsKey("bb-key"));
assertTrue(cl1.get(fqn("/a/b/c")).containsKey("c-key"));
assertTrue(cl1.get(fqn("/a/b/d")).containsKey("d-key"));
assertTrue(cl1.get(fqn("/e")).containsKey("e-key"));
assertTrue(cl1.get(fqn("/e/f/g")).containsKey("g-key"));
assertTrue(cl2.exists(fqn("/a")));
assertTrue(cl2.exists(fqn("/a")));
assertTrue(cl2.exists(fqn("/a/b")));
assertTrue(cl2.exists(fqn("/a/b")));
assertTrue(cl2.exists(fqn("/a/b/c")));
assertTrue(cl2.exists(fqn("/a/b/d")));
assertTrue(cl2.exists(fqn("/e")));
assertTrue(cl2.exists(fqn("/e/f/g")));
WaitForPushSingletonStoreCacheLoader scl2 = (WaitForPushSingletonStoreCacheLoader) cache2.getCacheLoaderManager().getCacheLoader();
CountDownLatch startPushLatch = new CountDownLatch(1);
scl2.setStartPushLatch(startPushLatch);
ViewChangeListener viewChangeListener = new ViewChangeListener(cache2);
stopCache1(false);
cache2.getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
cache2.getInvocationContext().getOptionOverrides().setSuppressPersistence(true);
cache2.put(fqn("/e/i"), "i-key", "i-value");
startPushLatch.countDown();
viewChangeListener.waitForViewChange(60, TimeUnit.SECONDS);
waitForPushStateCompletion(scl2.getPushStateFuture());
assertTrue(cl2.exists(fqn("/a")));
assertTrue(cl2.exists(fqn("/a")));
assertTrue(cl2.exists(fqn("/a/b")));
assertTrue(cl2.exists(fqn("/a/b")));
assertTrue(cl2.exists(fqn("/a/b/c")));
assertTrue(cl2.exists(fqn("/a/b/d")));
assertTrue(cl2.exists(fqn("/e")));
assertTrue(cl2.exists(fqn("/e/f/g")));
assertTrue(cl2.exists(fqn("/e/i")));
assertTrue(cl2.get(fqn("/a")).containsKey("a-key"));
assertTrue(cl2.get(fqn("/a")).containsKey("aa-key"));
assertTrue(cl2.get(fqn("/a/b")).containsKey("b-key"));
assertTrue(cl2.get(fqn("/a/b")).containsKey("bb-key"));
assertTrue(cl2.get(fqn("/a/b/c")).containsKey("c-key"));
assertTrue(cl2.get(fqn("/a/b/d")).containsKey("d-key"));
assertTrue(cl2.get(fqn("/e")).containsKey("e-key"));
assertTrue(cl2.get(fqn("/e/f/g")).containsKey("g-key"));
assertTrue(cl2.get(fqn("/e/i")).containsKey("i-key"));
cache2.put(fqn("/e/f/h"), "h-key", "h-value");
cache3.put(fqn("/i"), "i-key", "i-value");
assertTrue(cl2.get(fqn("/e/f/h")).containsKey("h-key"));
assertTrue(cl2.get(fqn("/i")).containsKey("i-key"));
assertTrue(cl3.exists(fqn("/a")));
assertTrue(cl3.exists(fqn("/a")));
assertTrue(cl3.exists(fqn("/a/b")));
assertTrue(cl3.exists(fqn("/a/b")));
assertTrue(cl3.exists(fqn("/a/b/c")));
assertTrue(cl3.exists(fqn("/a/b/d")));
assertTrue(cl3.exists(fqn("/e")));
assertTrue(cl3.exists(fqn("/e/f/g")));
assertTrue(cl3.exists(fqn("/e/f/h")));
assertTrue(cl3.exists(fqn("/i")));
viewChangeListener = new ViewChangeListener(cache3);
stopCache2(false);
viewChangeListener.waitForViewChange(60, TimeUnit.SECONDS);
SingletonStoreCacheLoader scl3 = (SingletonStoreCacheLoader) cache3.getCacheLoaderManager().getCacheLoader();
waitForPushStateCompletion(scl3.getPushStateFuture());
assertTrue(cl3.get(fqn("/a")).containsKey("a-key"));
assertTrue(cl3.get(fqn("/a")).containsKey("aa-key"));
assertTrue(cl3.get(fqn("/a/b")).containsKey("b-key"));
assertTrue(cl3.get(fqn("/a/b")).containsKey("bb-key"));
assertTrue(cl3.get(fqn("/a/b/c")).containsKey("c-key"));
assertTrue(cl3.get(fqn("/a/b/d")).containsKey("d-key"));
assertTrue(cl3.get(fqn("/e")).containsKey("e-key"));
assertTrue(cl3.get(fqn("/e/f/g")).containsKey("g-key"));
assertTrue(cl3.get(fqn("/e/f/h")).containsKey("h-key"));
assertTrue(cl3.get(fqn("/i")).containsKey("i-key"));
cache3.put(fqn("/a"), "aaa-key", "aaa-value");
assertTrue(cl3.get(fqn("/a")).containsKey("aaa-key"));
stopCache3(true);
}
public void testAvoidConcurrentStatePush() throws Exception
{
final ExecutorService executor = Executors.newFixedThreadPool(2);
final CountDownLatch pushStateCanFinish = new CountDownLatch(1);
final CountDownLatch secondActiveStatusChangerCanStart = new CountDownLatch(1);
final MockSingletonStoreCacheLoader mscl = new MockSingletonStoreCacheLoader(pushStateCanFinish, secondActiveStatusChangerCanStart, new SingletonStoreDefaultConfig());
Future f1 = executor.submit(createActiveStatusChanger(mscl));
secondActiveStatusChangerCanStart.await();
Future f2 = executor.submit(createActiveStatusChanger(mscl));
f1.get();
f2.get();
assertEquals(1, mscl.getNumberCreatedTasks());
}
public void testPushStateTimedOut() throws Exception
{
final CountDownLatch pushStateCanFinish = new CountDownLatch(1);
SingletonStoreDefaultConfig ssdc = new SingletonStoreDefaultConfig();
ssdc.setPushStateWhenCoordinatorTimeout(1000);
final MockSingletonStoreCacheLoader mscl = new MockSingletonStoreCacheLoader(pushStateCanFinish, null, ssdc);
Future f = Executors.newSingleThreadExecutor().submit(createActiveStatusChanger(mscl));
pushStateCanFinish.await(2000, TimeUnit.MILLISECONDS);
pushStateCanFinish.countDown();
try
{
f.get();
fail("Should have timed out");
}
catch (ExecutionException e)
{
Throwable t = e.getCause().getCause().getCause();
assertTrue(t + " should have been TimeoutException", t instanceof TimeoutException);
}
}
private void createCaches()
{
cache1.create();
cache2.create();
cache3.create();
}
private void statCaches()
{
cache1.start();
cache2.start();
cache3.start();
}
private void waitForPushStateCompletion(Future pushThreadFuture) throws Exception
{
if (pushThreadFuture != null)
{
pushThreadFuture.get();
}
}
private Callable<?> createActiveStatusChanger(SingletonStoreCacheLoader mscl)
{
return new ActiveStatusModifier(mscl);
}
protected CacheLoaderConfig getSingletonStoreCacheLoaderConfig(String cacheloaderClass, boolean isPush) throws Exception
{
CacheLoaderConfig clc = UnitTestConfigurationFactory.buildSingleCacheLoaderConfig(false, null, cacheloaderClass, "", false, false, false, false, false);
CacheLoaderConfig.IndividualCacheLoaderConfig.SingletonStoreConfig sc = new CacheLoaderConfig.IndividualCacheLoaderConfig.SingletonStoreConfig();
sc.setSingletonStoreEnabled(true);
sc.setProperties("pushStateWhenCoordinator = " + isPush + "\n pushStateWhenCoordinatorTimeout = 50000\n");
clc.getFirstCacheLoaderConfig().setSingletonStoreConfig(sc);
return clc;
}
protected CacheLoaderConfig getSingletonStoreCacheLoaderConfig(String cacheloaderClass, String singletonStoreClass) throws Exception
{
CacheLoaderConfig clc = UnitTestConfigurationFactory.buildSingleCacheLoaderConfig(false, null, cacheloaderClass, "", false, false, false, false, false);
CacheLoaderConfig.IndividualCacheLoaderConfig.SingletonStoreConfig sc = new CacheLoaderConfig.IndividualCacheLoaderConfig.SingletonStoreConfig();
sc.setSingletonStoreEnabled(true);
sc.setSingletonStoreClass(singletonStoreClass);
sc.setProperties("pushStateWhenCoordinator = true\n pushStateWhenCoordinatorTimeout = 50000\n");
clc.getFirstCacheLoaderConfig().setSingletonStoreConfig(sc);
return clc;
}
private void initSingletonNonPushCache(CacheSPI cache) throws Exception
{
cache.getConfiguration().setCacheLoaderConfig(getSingletonStoreCacheLoaderConfig(
SingletonDummyInMemoryCacheLoader.class.getName(), false));
}
private void initSingletonWithPushCache(CacheSPI cache) throws Exception
{
cache.getConfiguration().setCacheLoaderConfig(getSingletonStoreCacheLoaderConfig(
SingletonDummyInMemoryCacheLoader.class.getName(), true));
}
private void initSingletonWithWaitingPushCache(CacheSPI cache) throws Exception
{
cache.getConfiguration().setCacheLoaderConfig(getSingletonStoreCacheLoaderConfig(
SingletonDummyInMemoryCacheLoader.class.getName(), WaitForPushSingletonStoreCacheLoader.class.getName()));
}
private CacheLoader getDelegatingCacheLoader(CacheSPI cache)
{
AbstractDelegatingCacheLoader acl = (AbstractDelegatingCacheLoader) cache.getCacheLoaderManager().getCacheLoader();
return acl.getCacheLoader();
}
private Fqn fqn(String fqn)
{
return Fqn.fromString(fqn);
}
private void stopCache1(boolean clearCacheLoader)
{
if (cache1 != null)
{
TestingUtil.killCaches(clearCacheLoader, cache1);
}
cache1 = null;
}
private void stopCache2(boolean clearCacheLoader)
{
if (cache2 != null)
{
TestingUtil.killCaches(clearCacheLoader, cache2);
}
cache2 = null;
}
private void stopCache3(boolean clearCacheLoader)
{
if (cache3 != null)
{
TestingUtil.killCaches(clearCacheLoader, cache3);
}
cache3 = null;
}
@AfterMethod(alwaysRun = true)
public void tearDown()
{
stopCache1(true);
stopCache2(true);
stopCache3(true);
}
class MockSingletonStoreCacheLoader extends SingletonStoreCacheLoader
{
private int numberCreatedTasks = 0;
private CountDownLatch pushStateCanFinish;
private CountDownLatch secondActiveStatusChangerCanStart;
public MockSingletonStoreCacheLoader(CountDownLatch pushStateCanFinish, CountDownLatch secondActiveStatusChangerCanStart, SingletonStoreDefaultConfig config)
{
super(config);
this.pushStateCanFinish = pushStateCanFinish;
this.secondActiveStatusChangerCanStart = secondActiveStatusChangerCanStart;
}
public int getNumberCreatedTasks()
{
return numberCreatedTasks;
}
public void setNumberCreatedTasks(int numberCreatedTasks)
{
this.numberCreatedTasks = numberCreatedTasks;
}
@Override
protected Callable<?> createPushStateTask()
{
return new Callable()
{
public Object call() throws Exception
{
numberCreatedTasks++;
try
{
if (secondActiveStatusChangerCanStart != null)
{
secondActiveStatusChangerCanStart.countDown();
}
pushStateCanFinish.await();
}
catch (InterruptedException e)
{
fail("ActiveStatusModifier interrupted");
}
return null;
}
};
}
@Override
protected void awaitForPushToFinish(Future future, int timeout, TimeUnit unit)
{
pushStateCanFinish.countDown();
super.awaitForPushToFinish(future, timeout, unit);
}
}
class ActiveStatusModifier implements Callable
{
private SingletonStoreCacheLoader scl;
public ActiveStatusModifier(SingletonStoreCacheLoader singleton)
{
scl = singleton;
}
public Object call() throws Exception
{
log.debug("active status modifier started");
scl.activeStatusChanged(true);
scl.getPushStateFuture().get();
return null;
}
}
static class SingletonDummyInMemoryCacheLoader extends AbstractCacheLoader
{
static final DummyInMemoryCacheLoader singleton = new DummyInMemoryCacheLoader();
public boolean exists(Fqn name) throws Exception
{
return singleton.exists(name);
}
public Map<Object, Object> get(Fqn name) throws Exception
{
return singleton.get(name);
}
public Set<?> getChildrenNames(Fqn fqn) throws Exception
{
return singleton.getChildrenNames(fqn);
}
public IndividualCacheLoaderConfig getConfig()
{
return singleton.getConfig();
}
public Object put(Fqn name, Object key, Object value) throws Exception
{
return singleton.put(name, key, value);
}
public void put(Fqn name, Map<Object, Object> attributes) throws Exception
{
singleton.put(name, attributes);
}
public Object remove(Fqn fqn, Object key) throws Exception
{
return singleton.remove(fqn, key);
}
public void remove(Fqn fqn) throws Exception
{
singleton.remove(fqn);
}
public void removeData(Fqn fqn) throws Exception
{
singleton.removeData(fqn);
}
public void setConfig(IndividualCacheLoaderConfig config)
{
singleton.setConfig(config);
}
}
static class WaitForPushSingletonStoreCacheLoader extends SingletonStoreCacheLoader {
CountDownLatch startPushLatch;
WaitForPushSingletonStoreCacheLoader() {
}
public void setStartPushLatch(CountDownLatch startPushLatch) {
this.startPushLatch = startPushLatch;
}
@Override
protected void pushState(NodeSPI node) throws Exception {
startPushLatch.await();
super.pushState(node);
}
}
}