/*
* Copyright (C) 2009 eXo Platform SAS.
*
* 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.exoplatform.services.cache.impl.memcached;
import junit.framework.TestCase;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.services.cache.CacheListener;
import org.exoplatform.services.cache.CacheListenerContext;
import org.exoplatform.services.cache.CacheService;
import org.exoplatform.services.cache.CachedObjectSelector;
import org.exoplatform.services.cache.ExoCache;
import org.exoplatform.services.cache.ExoCacheFactory;
import org.exoplatform.services.cache.ObjectCacheInfo;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author <a href="mailto:nfilotto@exoplatform.com">Nicolas Filotto</a>
* @version $Id$
*
*/
public class TestMCExoCache extends TestCase
{
CacheService service;
MCExoCache<Serializable, Object> cache;
ExoCacheFactory factory;
MyCacheListener<Object> listener;
public TestMCExoCache(String name)
{
super(name);
}
public void setUp() throws Exception
{
this.service = (CacheService)PortalContainer.getInstance().getComponentInstanceOfType(CacheService.class);
this.cache = (MCExoCache<Serializable, Object>)service.getCacheInstance("myCache");
this.factory = (ExoCacheFactory)PortalContainer.getInstance().getComponentInstanceOfType(ExoCacheFactory.class);
listener = new MyCacheListener<Object>();
cache.addCacheListener(listener);
}
protected void tearDown() throws Exception
{
cache.clearCache();
}
public void testPut() throws Exception
{
assertEquals(0, listener.put);
cache.put(new MyKey("a"), "a");
cache.put(new MyKey("b"), "b");
cache.put(new MyKey("c"), "c");
cache.put(new MyKey("d"), null);
assertEquals(3, listener.put);
assertEquals(3, cache.getCacheSize());
assertEquals("a", cache.get(new MyKey("a")));
cache.put(new MyKey("a"), "c");
assertEquals(4, listener.put);
assertEquals(3, cache.getCacheSize());
assertEquals("c", cache.get(new MyKey("a")));
cache.put(new MyKey("d"), "c");
assertEquals(5, listener.put);
assertEquals(4, cache.getCacheSize());
}
public void testClearCache() throws Exception
{
cache.put(new MyKey("a"), "a");
cache.put(new MyKey("b"), "b");
cache.put(new MyKey("c"), "c");
assertEquals("a", cache.get(new MyKey("a")));
assertTrue(cache.getCacheSize() > 0);
assertEquals(0, listener.clearCache);
cache.clearCache();
assertEquals(1, listener.clearCache);
assertTrue(cache.getCacheSize() == 0);
assertNull(cache.get(new MyKey("a")));
}
public void testGet() throws Exception
{
assertEquals(0, cache.getCacheSize());
cache.put(new MyKey("a"), "a");
assertEquals(1, cache.getCacheSize());
assertEquals(0, listener.get);
assertEquals("a", cache.get(new MyKey("a")));
assertEquals(1, listener.get);
cache.put(new MyKey("a"), "c");
assertEquals(1, cache.getCacheSize());
assertEquals("c", cache.get(new MyKey("a")));
assertEquals(2, listener.get);
cache.remove(new MyKey("a"));
assertEquals(0, cache.getCacheSize());
assertNull(cache.get(new MyKey("a")));
assertNull(cache.get(new MyKey("x")));
assertEquals(4, listener.get);
}
public void testRemove() throws Exception
{
cache.put(new MyKey("a"), 1);
cache.put(new MyKey("b"), 2);
cache.put(new MyKey("c"), 3);
assertEquals(3, cache.getCacheSize());
assertEquals(0, listener.remove);
assertEquals(1, cache.remove(new MyKey("a")));
assertEquals(1, listener.remove);
assertEquals(2, cache.getCacheSize());
assertEquals(2, cache.remove(new MyKey("b")));
assertEquals(2, listener.remove);
assertEquals(1, cache.getCacheSize());
assertNull(cache.remove(new MyKey("x")));
assertEquals(2, listener.remove);
assertEquals(1, cache.getCacheSize());
}
public void testPutMap() throws Exception
{
Map<Serializable, Object> values = new HashMap<Serializable, Object>();
values.put(new MyKey("a"), "a");
values.put(new MyKey("b"), "b");
assertEquals(0, listener.put);
assertEquals(0, cache.getCacheSize());
cache.putMap(values);
assertEquals(2, listener.put);
assertEquals(2, cache.getCacheSize());
values = new HashMap<Serializable, Object>()
{
private static final long serialVersionUID = 1L;
public Set<Entry<Serializable, Object>> entrySet()
{
Set<Entry<Serializable, Object>> set = new LinkedHashSet<Entry<Serializable, Object>>(super.entrySet());
set.add(new Entry<Serializable, Object>()
{
public Object setValue(Object paramV)
{
return null;
}
public Object getValue()
{
throw new RuntimeException("An exception");
}
public Serializable getKey()
{
return "c";
}
});
return set;
}
};
values.put(new MyKey("e"), "e");
values.put(new MyKey("d"), "d");
cache.putMap(values);
assertEquals(4, listener.put);
assertEquals(4, cache.getCacheSize());
}
public void testGetCachedObjects() throws Exception
{
cache.put(new MyKey("a"), "a");
cache.put(new MyKey("b"), "b");
cache.put(new MyKey("c"), "c");
cache.put(new MyKey("d"), null);
assertEquals(3, cache.getCacheSize());
try
{
List<Object> values = cache.getCachedObjects();
assertEquals(3, values.size());
assertTrue(values.contains("a"));
assertTrue(values.contains("b"));
assertTrue(values.contains("c"));
}
catch (UnsupportedOperationException e)
{
// OK
}
}
public void testRemoveCachedObjects() throws Exception
{
cache.put(new MyKey("a"), "a");
cache.put(new MyKey("b"), "b");
cache.put(new MyKey("c"), "c");
cache.put(new MyKey("d"), null);
assertEquals(3, cache.getCacheSize());
try
{
List<Object> values = cache.removeCachedObjects();
assertEquals(3, values.size());
assertTrue(values.contains("a"));
assertTrue(values.contains("b"));
assertTrue(values.contains("c"));
assertEquals(0, cache.getCacheSize());
}
catch (UnsupportedOperationException e)
{
// OK
}
}
public void testSelect() throws Exception
{
cache.put(new MyKey("a"), 1);
cache.put(new MyKey("b"), 2);
cache.put(new MyKey("c"), 3);
final AtomicInteger count = new AtomicInteger();
CachedObjectSelector<Serializable, Object> selector = new CachedObjectSelector<Serializable, Object>()
{
public void onSelect(ExoCache<? extends Serializable, ? extends Object> cache, Serializable key,
ObjectCacheInfo<? extends Object> ocinfo) throws Exception
{
assertTrue(key.equals(new MyKey("a")) || key.equals(new MyKey("b")) || key.equals(new MyKey("c")));
assertTrue(ocinfo.get().equals(1) || ocinfo.get().equals(2) || ocinfo.get().equals(3));
count.incrementAndGet();
}
public boolean select(Serializable key, ObjectCacheInfo<? extends Object> ocinfo)
{
return true;
}
};
try
{
cache.select(selector);
assertEquals(3, count.intValue());
}
catch (UnsupportedOperationException e)
{
// OK
}
}
public void testGetHitsNMisses() throws Exception
{
int hits = cache.getCacheHit();
int misses = cache.getCacheMiss();
cache.put(new MyKey("a"), "a");
cache.get(new MyKey("a"));
cache.remove(new MyKey("a"));
cache.get(new MyKey("a"));
cache.get(new MyKey("z"));
assertEquals(1, cache.getCacheHit() - hits);
assertEquals(2, cache.getCacheMiss() - misses);
}
public void testMultiThreading() throws Exception
{
final ExoCache<Serializable, Object> cache = service.getCacheInstance("test-multi-threading");
final int totalElement = 100;
final int totalTimes = 20;
int reader = 20;
int writer = 10;
int remover = 5;
int cleaner = 1;
final CountDownLatch startSignalWriter = new CountDownLatch(1);
final CountDownLatch startSignalOthers = new CountDownLatch(1);
final CountDownLatch doneSignal = new CountDownLatch(reader + writer + remover);
final List<Exception> errors = Collections.synchronizedList(new ArrayList<Exception>());
for (int i = 0; i < writer; i++)
{
final int index = i;
Thread thread = new Thread()
{
public void run()
{
try
{
startSignalWriter.await();
for (int j = 0; j < totalTimes; j++)
{
for (int i = 0; i < totalElement; i++)
{
cache.put(new MyKey("key" + i), "value" + i);
}
if (index == 0 && j == 0)
{
// The cache is full, we can launch the others
startSignalOthers.countDown();
}
sleep(50);
}
}
catch (Exception e)
{
errors.add(e);
}
finally
{
doneSignal.countDown();
}
}
};
thread.start();
}
startSignalWriter.countDown();
for (int i = 0; i < reader; i++)
{
Thread thread = new Thread()
{
public void run()
{
try
{
startSignalOthers.await();
for (int j = 0; j < totalTimes; j++)
{
for (int i = 0; i < totalElement; i++)
{
cache.get(new MyKey("key" + i));
}
sleep(50);
}
}
catch (Exception e)
{
errors.add(e);
}
finally
{
doneSignal.countDown();
}
}
};
thread.start();
}
for (int i = 0; i < remover; i++)
{
Thread thread = new Thread()
{
public void run()
{
try
{
startSignalOthers.await();
for (int j = 0; j < totalTimes; j++)
{
for (int i = 0; i < totalElement; i++)
{
cache.remove(new MyKey("key" + i));
}
sleep(50);
}
}
catch (Exception e)
{
errors.add(e);
}
finally
{
doneSignal.countDown();
}
}
};
thread.start();
}
doneSignal.await();
for (int i = 0; i < totalElement; i++)
{
cache.put(new MyKey("key" + i), "value" + i);
}
assertEquals(totalElement, cache.getCacheSize());
final CountDownLatch startSignal = new CountDownLatch(1);
final CountDownLatch doneSignal2 = new CountDownLatch(writer + cleaner);
for (int i = 0; i < writer; i++)
{
Thread thread = new Thread()
{
public void run()
{
try
{
startSignal.await();
for (int j = 0; j < totalTimes; j++)
{
for (int i = 0; i < totalElement; i++)
{
cache.put(new MyKey("key" + i), "value" + i);
}
sleep(50);
}
}
catch (Exception e)
{
errors.add(e);
}
finally
{
doneSignal2.countDown();
}
}
};
thread.start();
}
for (int i = 0; i < cleaner; i++)
{
Thread thread = new Thread()
{
public void run()
{
try
{
startSignal.await();
for (int j = 0; j < totalTimes; j++)
{
sleep(150);
cache.clearCache();
}
}
catch (Exception e)
{
errors.add(e);
}
finally
{
doneSignal2.countDown();
}
}
};
thread.start();
}
assertTrue(cache.getCacheSize() > 0);
cache.clearCache();
assertEquals(0, cache.getCacheSize());
if (!errors.isEmpty())
{
for (Exception e : errors)
{
e.printStackTrace();
}
throw errors.get(0);
}
}
public static class MyCacheListener<T> implements CacheListener<Serializable, T>
{
public int clearCache;
public int expire;
public int get;
public int put;
public int remove;
public void onClearCache(CacheListenerContext context) throws Exception
{
clearCache++;
}
public void onExpire(CacheListenerContext context, Serializable key, T obj) throws Exception
{
expire++;
}
public void onGet(CacheListenerContext context, Serializable key, T obj) throws Exception
{
get++;
}
public void onPut(CacheListenerContext context, Serializable key, T obj) throws Exception
{
put++;
}
public void onRemove(CacheListenerContext context, Serializable key, T obj) throws Exception
{
remove++;
}
}
public static class MyKey implements Serializable
{
private static final long serialVersionUID = 1L;
public String value;
public MyKey(String value)
{
this.value = value;
}
@Override
public boolean equals(Object paramObject)
{
return paramObject instanceof MyKey && ((MyKey)paramObject).value.equals(value);
}
@Override
public int hashCode()
{
return value.hashCode();
}
@Override
public String toString()
{
return value;
}
}
}