/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.map.impl.mapstore;
import com.hazelcast.config.Config;
import com.hazelcast.config.MapConfig;
import com.hazelcast.config.MapStoreConfig;
import com.hazelcast.config.MapStoreConfig.InitialLoadMode;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.core.MapStore;
import com.hazelcast.core.MapStoreAdapter;
import com.hazelcast.core.TransactionalMap;
import com.hazelcast.map.impl.MapService;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.map.impl.mapstore.MapStoreTest.MapStoreWithStoreCount;
import com.hazelcast.map.impl.mapstore.MapStoreTest.SimpleMapStore;
import com.hazelcast.map.impl.mapstore.MapStoreTest.TestMapStore;
import com.hazelcast.map.impl.mapstore.writebehind.TestMapUsingMapStoreBuilder;
import com.hazelcast.map.impl.mapstore.writebehind.WriteBehindStore;
import com.hazelcast.map.impl.recordstore.RecordStore;
import com.hazelcast.spi.impl.NodeEngineImpl;
import com.hazelcast.spi.properties.GroupProperty;
import com.hazelcast.test.AssertTask;
import com.hazelcast.test.HazelcastParallelClassRunner;
import com.hazelcast.test.TestHazelcastInstanceFactory;
import com.hazelcast.test.annotation.NightlyTest;
import com.hazelcast.test.annotation.ParallelTest;
import com.hazelcast.test.annotation.QuickTest;
import com.hazelcast.transaction.TransactionContext;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(HazelcastParallelClassRunner.class)
@Category({QuickTest.class, ParallelTest.class})
public class MapStoreWriteBehindTest extends AbstractMapStoreTest {
@Test(timeout = 120000)
public void testOneMemberWriteBehindWithMaxIdle() throws Exception {
final EventBasedMapStore testMapStore = new EventBasedMapStore();
Config config = newConfig(testMapStore, 5, InitialLoadMode.EAGER);
config.setProperty(GroupProperty.PARTITION_COUNT.getName(), "1");
config.getMapConfig("default").setMaxIdleSeconds(10);
HazelcastInstance instance = createHazelcastInstance(config);
final IMap<Integer, String> map = instance.getMap("default");
final int total = 10;
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
assertEquals(EventBasedMapStore.STORE_EVENTS.LOAD_ALL_KEYS, testMapStore.getEvents().poll());
}
});
for (int i = 0; i < total; i++) {
map.put(i, "value" + i);
}
sleepSeconds(11);
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
assertEquals(0, map.size());
}
});
assertEquals(total, testMapStore.getStore().size());
}
@Test(timeout = 120000)
public void testOneMemberWriteBehindWithEvictions() throws Exception {
final String mapName = "testOneMemberWriteBehindWithEvictions";
final EventBasedMapStore testMapStore = new EventBasedMapStore();
testMapStore.loadAllLatch = new CountDownLatch(1);
final Config config = newConfig(testMapStore, 2, InitialLoadMode.EAGER);
final HazelcastInstance instance = createHazelcastInstance(config);
final IMap<Integer, String> map = instance.getMap(mapName);
// check if load all called.
assertTrue("map store loadAllKeys must be called", testMapStore.loadAllLatch.await(10, SECONDS));
// map population count.
final int populationCount = 100;
// latch for store & storeAll events.
testMapStore.storeLatch = new CountDownLatch(populationCount);
//populate map.
for (int i = 0; i < populationCount; i++) {
map.put(i, "value" + i);
}
//wait for all store ops.
assertOpenEventually(testMapStore.storeLatch);
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
assertEquals(0, writeBehindQueueSize(instance, mapName));
}
});
// init before eviction
testMapStore.storeLatch = new CountDownLatch(populationCount);
//evict.
for (int i = 0; i < populationCount; i++) {
map.evict(i);
}
// expect no store op
assertEquals(populationCount, testMapStore.storeLatch.getCount());
// check store size
assertEquals(populationCount, testMapStore.getStore().size());
// check map size
assertEquals(0, map.size());
// re-populate map
for (int i = 0; i < populationCount; i++) {
map.put(i, "value" + i);
}
// evict again
for (int i = 0; i < populationCount; i++) {
map.evict(i);
}
// wait for all store ops
testMapStore.storeLatch.await(10, SECONDS);
// check store size
assertEquals(populationCount, testMapStore.getStore().size());
// check map size
assertEquals(0, map.size());
// re-populate map
for (int i = 0; i < populationCount; i++) {
map.put(i, "value" + i);
}
testMapStore.deleteLatch = new CountDownLatch(populationCount);
// clear map
for (int i = 0; i < populationCount; i++) {
map.remove(i);
}
testMapStore.deleteLatch.await(10, SECONDS);
// check map size
assertEquals(0, map.size());
}
private int writeBehindQueueSize(HazelcastInstance node, String mapName) {
int size = 0;
final NodeEngineImpl nodeEngine = getNode(node).getNodeEngine();
MapService mapService = nodeEngine.getService(MapService.SERVICE_NAME);
final MapServiceContext mapServiceContext = mapService.getMapServiceContext();
final int partitionCount = nodeEngine.getPartitionService().getPartitionCount();
for (int i = 0; i < partitionCount; i++) {
final RecordStore recordStore = mapServiceContext.getExistingRecordStore(i, mapName);
if (recordStore == null) {
continue;
}
final MapDataStore mapDataStore = recordStore.getMapDataStore();
size += ((WriteBehindStore) mapDataStore).getWriteBehindQueue().size();
}
return size;
}
@Test(timeout = 120000)
public void testOneMemberWriteBehind() throws Exception {
MapStoreTest.TestMapStore testMapStore = new TestMapStore(1, 1, 1);
testMapStore.setLoadAllKeys(false);
Config config = newConfig(testMapStore, 5);
TestHazelcastInstanceFactory nodeFactory = createHazelcastInstanceFactory(3);
HazelcastInstance instance = nodeFactory.newHazelcastInstance(config);
testMapStore.insert("1", "value1");
IMap<String, String> map = instance.getMap("default");
assertEquals(0, map.size());
assertEquals("value1", map.get("1"));
assertEquals("value1", map.put("1", "value2"));
assertEquals("value2", map.get("1"));
// store should have the old data as we will write-behind
assertEquals("value1", testMapStore.getStore().get("1"));
assertEquals(1, map.size());
map.flush();
assertTrue(map.evict("1"));
assertEquals("value2", testMapStore.getStore().get("1"));
assertEquals(0, map.size());
assertEquals(1, testMapStore.getStore().size());
assertEquals("value2", map.get("1"));
assertEquals(1, map.size());
map.remove("1");
// store should have the old data as we will delete-behind
assertEquals(1, testMapStore.getStore().size());
assertEquals(0, map.size());
testMapStore.assertAwait(100);
assertEquals(0, testMapStore.getStore().size());
}
@Test(timeout = 120000)
public void testWriteBehindUpdateSameKey() throws Exception {
final TestMapStore testMapStore = new TestMapStore(2, 0, 0);
testMapStore.setLoadAllKeys(false);
Config config = newConfig(testMapStore, 5);
TestHazelcastInstanceFactory nodeFactory = createHazelcastInstanceFactory(2);
HazelcastInstance instance = nodeFactory.newHazelcastInstance(config);
nodeFactory.newHazelcastInstance(config);
IMap<Object, Object> map = instance.getMap("map");
map.put("key", "value");
Thread.sleep(2000);
map.put("key", "value2");
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
assertEquals("value2", testMapStore.getStore().get("key"));
}
});
}
@Test(timeout = 120000)
public void testOneMemberWriteBehindFlush() throws Exception {
TestMapStore testMapStore = new TestMapStore(1, 1, 1);
testMapStore.setLoadAllKeys(false);
int writeDelaySeconds = 2;
Config config = newConfig(testMapStore, writeDelaySeconds);
TestHazelcastInstanceFactory nodeFactory = createHazelcastInstanceFactory(3);
HazelcastInstance instance = nodeFactory.newHazelcastInstance(config);
IMap<String, String> map = instance.getMap("default");
assertEquals(0, map.size());
long timeBeforePut = System.nanoTime();
assertEquals("Map produced a value out of thin air", null, map.put("1", "value1"));
assertEquals("Map did not return a previously stored value", "value1", map.get("1"));
String mapStoreValue = (String) testMapStore.getStore().get("1");
if (mapStoreValue != null) {
assertMapStoreDidNotFlushValueTooSoon(testMapStore, writeDelaySeconds, timeBeforePut);
assertEquals("value1", mapStoreValue);
}
assertEquals(1, map.size());
map.flush();
assertEquals("value1", testMapStore.getStore().get("1"));
}
private void assertMapStoreDidNotFlushValueTooSoon(TestMapStore testMapStore, int writeDelaySeconds, long timeBeforePutNanos) {
double lenientFactor = 0.9;
long lastUpdateTimestamp = testMapStore.getLastStoreNanos();
double minimumFlushNanos = timeBeforePutNanos + (lenientFactor * SECONDS.toNanos(writeDelaySeconds));
assertTrue("WriteBehind Queue was flushed too soon. Configured write delay: "
+ SECONDS.toMillis(writeDelaySeconds) + " ms. But it took less than "
+ NANOSECONDS.toMillis(lastUpdateTimestamp - timeBeforePutNanos) + " ms to flush the queue.",
lastUpdateTimestamp >= minimumFlushNanos);
}
@Test(timeout = 120000)
public void testOneMemberWriteBehind2() throws Exception {
final EventBasedMapStore testMapStore = new EventBasedMapStore();
testMapStore.setLoadAllKeys(false);
Config config = newConfig(testMapStore, 1, InitialLoadMode.EAGER);
HazelcastInstance instance = createHazelcastInstance(config);
IMap<String, String> map = instance.getMap("default");
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
Object event = testMapStore.getEvents().poll();
assertEquals(EventBasedMapStore.STORE_EVENTS.LOAD_ALL_KEYS, event);
}
});
map.put("1", "value1");
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
assertEquals(EventBasedMapStore.STORE_EVENTS.LOAD, testMapStore.getEvents().poll());
}
});
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
assertEquals(EventBasedMapStore.STORE_EVENTS.STORE, testMapStore.getEvents().poll());
}
});
map.remove("1");
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
assertEquals(EventBasedMapStore.STORE_EVENTS.DELETE, testMapStore.getEvents().poll());
}
});
}
@Test(timeout = 120000)
@Category(NightlyTest.class)
// issue #2747: when MapStore configured with write behind, distributed objects' destroy method does not work
public void testWriteBehindDestroy() throws InterruptedException {
final int writeDelaySeconds = 5;
String mapName = randomMapName();
final MapStore<String, String> store = new SimpleMapStore<String, String>();
Config config = newConfig(mapName, store, writeDelaySeconds);
HazelcastInstance hzInstance = createHazelcastInstance(config);
IMap<String, String> map = hzInstance.getMap(mapName);
map.put("key", "value");
map.destroy();
sleepSeconds(2 * writeDelaySeconds);
assertNotEquals("value", store.load("key"));
}
@Test(timeout = 120000)
public void testKeysWithPredicateShouldLoadMapStore() throws InterruptedException {
EventBasedMapStore<String, Integer> testMapStore = new EventBasedMapStore<String, Integer>()
.insert("key1", 17)
.insert("key2", 23)
.insert("key3", 47);
HazelcastInstance instance = createHazelcastInstance(newConfig(testMapStore, 0));
final IMap map = instance.getMap("default");
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
Set result = map.keySet();
assertContains(result, "key1");
assertContains(result, "key2");
assertContains(result, "key3");
}
});
}
@Test(timeout = 120000)
public void testIssue1085WriteBehindBackup() throws InterruptedException {
Config config = getConfig();
String name = "testIssue1085WriteBehindBackup";
MapConfig writeBehindBackup = config.getMapConfig(name);
MapStoreConfig mapStoreConfig = new MapStoreConfig();
mapStoreConfig.setWriteDelaySeconds(5);
int size = 1000;
MapStoreWithStoreCount mapStore = new MapStoreWithStoreCount(size, 120);
mapStoreConfig.setImplementation(mapStore);
writeBehindBackup.setMapStoreConfig(mapStoreConfig);
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(3);
HazelcastInstance instance = factory.newHazelcastInstance(config);
HazelcastInstance instance2 = factory.newHazelcastInstance(config);
final IMap<Integer, Integer> map = instance.getMap(name);
for (int i = 0; i < size; i++) {
map.put(i, i);
}
instance2.getLifecycleService().shutdown();
mapStore.awaitStores();
}
@Test(timeout = 120000)
public void testIssue1085WriteBehindBackupWithLongRunnigMapStore() throws InterruptedException {
final String name = randomMapName("testIssue1085WriteBehindBackup");
final int expectedStoreCount = 3;
final int nodeCount = 3;
Config config = getConfig();
config.setProperty(GroupProperty.MAP_REPLICA_SCHEDULED_TASK_DELAY_SECONDS.getName(), "30");
MapConfig writeBehindBackupConfig = config.getMapConfig(name);
MapStoreConfig mapStoreConfig = new MapStoreConfig();
mapStoreConfig.setWriteDelaySeconds(5);
final MapStoreWithStoreCount mapStore = new MapStoreWithStoreCount(expectedStoreCount, 300, 50);
mapStoreConfig.setImplementation(mapStore);
writeBehindBackupConfig.setMapStoreConfig(mapStoreConfig);
// create nodes.
final TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(nodeCount);
HazelcastInstance node1 = factory.newHazelcastInstance(config);
HazelcastInstance node2 = factory.newHazelcastInstance(config);
HazelcastInstance node3 = factory.newHazelcastInstance(config);
// create corresponding keys.
final String keyOwnedByNode1 = generateKeyOwnedBy(node1);
final String keyOwnedByNode2 = generateKeyOwnedBy(node2);
final String keyOwnedByNode3 = generateKeyOwnedBy(node3);
// put one key value pair per node.
final IMap<String, Integer> map = node1.getMap(name);
map.put(keyOwnedByNode1, 1);
map.put(keyOwnedByNode2, 2);
map.put(keyOwnedByNode3, 3);
// shutdown node2.
node2.getLifecycleService().shutdown();
// wait store ops. finish.
mapStore.awaitStores();
// we should see at least expected store count.
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
int storeOperationCount = mapStore.count.intValue();
assertTrue("expected: " + expectedStoreCount
+ ", actual: " + storeOperationCount, expectedStoreCount <= storeOperationCount);
}
});
}
@Test(timeout = 120000)
public void testMapDelete_whenLoadFails() throws Exception {
final FailingLoadMapStore mapStore = new FailingLoadMapStore();
final IMap<Object, Object> map = TestMapUsingMapStoreBuilder.create()
.withMapStore(mapStore)
.withNodeCount(1)
.withNodeFactory(createHazelcastInstanceFactory(1))
.build();
try {
map.delete(1);
} catch (IllegalStateException e) {
fail();
}
}
@Test(timeout = 120000, expected = IllegalStateException.class)
public void testMapRemove_whenMapStoreLoadFails() throws Exception {
final FailingLoadMapStore mapStore = new FailingLoadMapStore();
final IMap<Object, Object> map = TestMapUsingMapStoreBuilder.create()
.withMapStore(mapStore)
.withNodeCount(1)
.withNodeFactory(createHazelcastInstanceFactory(1))
.build();
map.remove(1);
}
@Test(timeout = 120000)
public void testIssue1085WriteBehindBackupTransactional() throws InterruptedException {
final String name = randomMapName();
final int size = 1000;
MapStoreTest.MapStoreWithStoreCount mapStore = new MapStoreTest.MapStoreWithStoreCount(size, 120);
Config config = newConfig(name, mapStore, 5);
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(3);
HazelcastInstance instance = factory.newHazelcastInstance(config);
HazelcastInstance instance2 = factory.newHazelcastInstance(config);
TransactionContext context = instance.newTransactionContext();
context.beginTransaction();
TransactionalMap<Object, Object> tmap = context.getMap(name);
for (int i = 0; i < size; i++) {
tmap.put(i, i);
}
context.commitTransaction();
instance2.getLifecycleService().shutdown();
mapStore.awaitStores();
}
@Test(timeout = 120000)
public void testWriteBehindSameSecondSameKey() throws Exception {
final TestMapStore testMapStore = new TestMapStore(100, 0, 0); // In some cases 2 store operation may happened
testMapStore.setLoadAllKeys(false);
Config config = newConfig(testMapStore, 2);
HazelcastInstance instance = createHazelcastInstance(config);
IMap<Object, Object> map = instance.getMap("testWriteBehindSameSecondSameKey");
final int size1 = 20;
final int size2 = 10;
for (int i = 0; i < size1; i++) {
map.put("key", "value" + i);
}
for (int i = 0; i < size2; i++) {
map.put("key" + i, "value" + i);
}
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
assertEquals("value" + (size1 - 1), testMapStore.getStore().get("key"));
}
});
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
assertEquals("value" + (size2 - 1), testMapStore.getStore().get("key" + (size2 - 1)));
}
});
}
@Test(timeout = 120000)
public void testWriteBehindWriteRemoveOrderOfSameKey() throws Exception {
final String mapName = randomMapName("_testWriteBehindWriteRemoveOrderOfSameKey_");
final int iterationCount = 5;
final int delaySeconds = 1;
final int putOps = 3;
final int removeOps = 2;
final int expectedStoreSizeEventually = 1;
final RecordingMapStore store = new RecordingMapStore(iterationCount * putOps, iterationCount * removeOps);
final Config config = newConfig(mapName, store, delaySeconds);
final HazelcastInstance node = createHazelcastInstance(config);
final IMap<String, String> map = node.getMap(mapName);
String key = "key";
for (int i = 0; i < iterationCount; i++) {
String value = "value" + i;
map.put(key, value);
map.remove(key);
map.put(key, value);
map.remove(key);
map.put(key, value);
}
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
assertEquals(expectedStoreSizeEventually, store.getStore().size());
}
});
assertEquals("value" + (iterationCount - 1), map.get(key));
}
@Test(timeout = 120000)
public void mapStore_setOnIMapDoesNotRemoveKeyFromWriteBehindDeleteQueue() throws Exception {
MapStoreConfig mapStoreConfig = new MapStoreConfig()
.setEnabled(true)
.setImplementation(new SimpleMapStore<String, String>())
.setWriteDelaySeconds(Integer.MAX_VALUE);
Config config = getConfig();
config.getMapConfig("map").setMapStoreConfig(mapStoreConfig);
HazelcastInstance instance = createHazelcastInstance(config);
IMap<String, String> map = instance.getMap("map");
map.put("foo", "bar");
map.remove("foo");
map.set("foo", "bar");
assertEquals("bar", map.get("foo"));
}
@Test(timeout = 120000)
public void testDelete_thenPutIfAbsent_withWriteBehindEnabled() throws Exception {
TestMapStore testMapStore = new TestMapStore(1, 1, 1);
Config config = newConfig(testMapStore, 100);
TestHazelcastInstanceFactory nodeFactory = createHazelcastInstanceFactory(1);
HazelcastInstance instance = nodeFactory.newHazelcastInstance(config);
IMap<Integer, Integer> map = instance.getMap("default");
map.put(1, 1);
map.delete(1);
final Object putIfAbsent = map.putIfAbsent(1, 2);
assertNull(putIfAbsent);
}
public static class RecordingMapStore implements MapStore<String, String> {
private static final boolean DEBUG = false;
private final CountDownLatch expectedStore;
private final CountDownLatch expectedRemove;
private final ConcurrentHashMap<String, String> store;
RecordingMapStore(int expectedStore, int expectedRemove) {
this.expectedStore = new CountDownLatch(expectedStore);
this.expectedRemove = new CountDownLatch(expectedRemove);
this.store = new ConcurrentHashMap<String, String>();
}
public ConcurrentHashMap<String, String> getStore() {
return store;
}
@Override
public String load(String key) {
log("load(" + key + ") called.");
return store.get(key);
}
@Override
public Map<String, String> loadAll(Collection<String> keys) {
if (DEBUG) {
List<String> keysList = new ArrayList<String>(keys);
Collections.sort(keysList);
log("loadAll(" + keysList + ") called.");
}
Map<String, String> result = new HashMap<String, String>();
for (String key : keys) {
String value = store.get(key);
if (value != null) {
result.put(key, value);
}
}
return result;
}
@Override
public Set<String> loadAllKeys() {
log("loadAllKeys() called.");
Set<String> result = new HashSet<String>(store.keySet());
log("loadAllKeys result = " + result);
return result;
}
@Override
public void store(String key, String value) {
log("store(" + key + ") called.");
String valuePrev = store.put(key, value);
expectedStore.countDown();
if (valuePrev != null) {
log("- Unexpected Update (operations reordered?): " + key);
}
}
@Override
public void storeAll(Map<String, String> map) {
if (DEBUG) {
TreeSet<String> setSorted = new TreeSet<String>(map.keySet());
log("storeAll(" + setSorted + ") called.");
}
store.putAll(map);
final int size = map.keySet().size();
for (int i = 0; i < size; i++) {
expectedStore.countDown();
}
}
@Override
public void delete(String key) {
log("delete(" + key + ") called.");
String valuePrev = store.remove(key);
expectedRemove.countDown();
if (valuePrev == null) {
log("- Unnecessary delete (operations reordered?): " + key);
}
}
@Override
public void deleteAll(Collection<String> keys) {
if (DEBUG) {
List<String> keysList = new ArrayList<String>(keys);
Collections.sort(keysList);
log("deleteAll(" + keysList + ") called.");
}
for (String key : keys) {
String valuePrev = store.remove(key);
expectedRemove.countDown();
if (valuePrev == null) {
log("- Unnecessary delete (operations reordered?): " + key);
}
}
}
private void log(String msg) {
if (DEBUG) {
System.out.println(msg);
}
}
}
public static class FailAwareMapStore implements MapStore {
final Map<Object, Object> db = new ConcurrentHashMap<Object, Object>();
final AtomicLong deletes = new AtomicLong();
final AtomicLong deleteAlls = new AtomicLong();
final AtomicLong stores = new AtomicLong();
final AtomicLong storeAlls = new AtomicLong();
final AtomicLong loads = new AtomicLong();
final AtomicLong loadAlls = new AtomicLong();
final AtomicLong loadAllKeys = new AtomicLong();
final AtomicBoolean storeFail = new AtomicBoolean(false);
final AtomicBoolean loadFail = new AtomicBoolean(false);
final List<BlockingQueue<Object>> listeners = new CopyOnWriteArrayList<BlockingQueue<Object>>();
public void addListener(BlockingQueue<Object> obj) {
listeners.add(obj);
}
public void notifyListeners() {
for (BlockingQueue<Object> listener : listeners) {
listener.offer(new Object());
}
}
@Override
public void delete(Object key) {
try {
if (storeFail.get()) {
throw new RuntimeException();
} else {
db.remove(key);
}
} finally {
deletes.incrementAndGet();
notifyListeners();
}
}
public void setFail(boolean shouldFail, boolean loadFail) {
this.storeFail.set(shouldFail);
this.loadFail.set(loadFail);
}
@Override
public void store(Object key, Object value) {
try {
if (storeFail.get()) {
throw new RuntimeException();
} else {
db.put(key, value);
}
} finally {
stores.incrementAndGet();
notifyListeners();
}
}
@Override
public Set loadAllKeys() {
try {
return db.keySet();
} finally {
loadAllKeys.incrementAndGet();
}
}
@Override
public Object load(Object key) {
try {
if (loadFail.get()) {
throw new RuntimeException();
} else {
return db.get(key);
}
} finally {
loads.incrementAndGet();
}
}
@Override
public void storeAll(Map map) {
try {
if (storeFail.get()) {
throw new RuntimeException();
} else {
db.putAll(map);
}
} finally {
storeAlls.incrementAndGet();
notifyListeners();
}
}
@Override
public Map loadAll(Collection keys) {
try {
if (loadFail.get()) {
throw new RuntimeException();
} else {
Map<Object, Object> results = new HashMap<Object, Object>();
for (Object key : keys) {
Object value = db.get(key);
if (value != null) {
results.put(key, value);
}
}
return results;
}
} finally {
loadAlls.incrementAndGet();
notifyListeners();
}
}
@Override
public void deleteAll(Collection keys) {
try {
if (storeFail.get()) {
throw new RuntimeException();
} else {
for (Object key : keys) {
db.remove(key);
}
}
} finally {
deleteAlls.incrementAndGet();
notifyListeners();
}
}
}
class FailingLoadMapStore extends MapStoreAdapter<Object, Object> {
@Override
public Object load(Object key) {
throw new IllegalStateException();
}
}
}