package com.jivesoftware.os.amzabot.deployable;
import com.google.common.collect.Maps;
import com.jivesoftware.os.mlogger.core.AtomicCounter;
import com.jivesoftware.os.mlogger.core.MetricLogger;
import com.jivesoftware.os.mlogger.core.MetricLoggerFactory;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang.RandomStringUtils;
public class AmzaKeyClearingHouse {
private static final MetricLogger LOG = MetricLoggerFactory.getLogger();
private final Random RANDOM = new Random();
private final AtomicBoolean honorCapacity = new AtomicBoolean(false);
private final AtomicCounter currentCapacity = new AtomicCounter();
// key, value
private ConcurrentMap<String, Integer> keyMap = Maps.newConcurrentMap();
AmzaKeyClearingHouse() {
}
AmzaKeyClearingHouse(long capacity) {
honorCapacity.set(true);
currentCapacity.set(capacity);
}
public ConcurrentMap<String, Integer> getKeyMap() {
return keyMap;
}
public void clearKeyMap() {
keyMap.clear();
}
// key, <expected value, actual value>
private ConcurrentMap<String, Entry<Integer, Integer>> quarantinedKeyMap = Maps.newConcurrentMap();
public ConcurrentMap<String, Entry<Integer, Integer>> getQuarantinedKeyMap() {
return quarantinedKeyMap;
}
public void clearQuarantinedKeyMap() {
quarantinedKeyMap.clear();
}
public Integer set(String key, String value) {
return keyMap.put(key, value.hashCode());
}
public Integer get(String key) {
return keyMap.get(key);
}
public Integer delete(String key) {
return keyMap.remove(key);
}
public String genRandomValue(int valueSizeThreshold) {
return RandomStringUtils.randomAlphanumeric(RANDOM.nextInt(valueSizeThreshold));
}
public Entry<String, String> genRandomEntry(String key, int valueSizeThreshold) {
if (honorCapacity.get() && currentCapacity.getValue() < 1) {
return null;
}
currentCapacity.dec();
return new SimpleEntry<>(
key,
genRandomValue(valueSizeThreshold));
}
public Entry<String, Integer> popRandomEntry() {
if (keyMap.isEmpty()) {
return null;
}
List<Entry<String, Integer>> array = new ArrayList<>(keyMap.entrySet());
Entry<String, Integer> entry = array.get(RANDOM.nextInt(keyMap.size()));
keyMap.remove(entry.getKey());
return entry;
}
public Entry<String, Integer> getRandomEntry() {
if (keyMap.isEmpty()) {
return null;
}
List<Entry<String, Integer>> array = new ArrayList<>(keyMap.entrySet());
return array.get(RANDOM.nextInt(keyMap.size()));
}
public void quarantineEntry(Entry<String, Integer> entry, Integer value) {
LOG.error("Quarantine entry {}:{}:{}", entry.getKey(), entry.getValue(), value);
quarantinedKeyMap.put(entry.getKey(),
new SimpleEntry<>(entry.getValue().hashCode(), value));
keyMap.remove(entry.getKey());
}
public boolean verifyKeyMap(Map<String, String> partitionMap) throws Exception {
boolean res = true;
LOG.info("Verify key map with partition snapshot of size {}.", partitionMap.size());
Map<String, Integer> keyMapCopy = new HashMap<>(keyMap);
for (Entry<String, String> entry : partitionMap.entrySet()) {
Integer value = keyMapCopy.remove(entry.getKey());
if (value == null) {
quarantineEntry(new SimpleEntry<>(entry.getKey(), entry.getValue().hashCode()), null);
LOG.error("Key's value not found {}:{}:{}",
entry.getKey(),
AmzaBotUtil.truncVal(entry.getValue()),
null);
res = false;
} else if (entry.getValue().hashCode() != value) {
quarantineEntry(new SimpleEntry<>(entry.getKey(), entry.getValue().hashCode()), value);
LOG.error("Key's value differs {}:{}:{}",
entry.getKey(),
entry.getValue().hashCode(),
value);
res = false;
}
}
for (Entry<String, Integer> entry : keyMapCopy.entrySet()) {
quarantineEntry(entry, -1);
LOG.error("Extra key found in clearing house {}:{}",
entry.getKey(),
entry.getValue());
res = false;
}
if (res) {
LOG.info("Clearing house is clean");
} else {
LOG.error("Clearing house is unclean");
}
return res;
}
}