package com.mpush.zk;
import com.google.common.collect.Maps;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.KeeperException;
import java.io.*;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Created by Stream.Liu
*/
public class ZKSyncMap<K, V> implements Map<K, V> {
static final String ZK_PATH_SYNC_MAP = "syncMap";
private final CuratorFramework curator;
private final String mapPath;
private final String mapName;
public ZKSyncMap(CuratorFramework curator, String mapName) {
this.curator = curator;
this.mapName = mapName;
this.mapPath = "/" + ZK_PATH_SYNC_MAP + "/" + mapName;
}
@Override
public int size() {
try {
return curator.getChildren().forPath(mapPath).size();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isEmpty() {
try {
return curator.getChildren().forPath(mapPath).isEmpty();
} catch (Exception e) {
throw new ZKException(e);
}
}
@Override
public boolean containsKey(Object key) {
try {
return curator.checkExists().forPath(keyPath(key)) != null;
} catch (Exception e) {
throw new ZKException(e);
}
}
@Override
public boolean containsValue(Object value) {
try {
return curator.getChildren().forPath(mapPath).stream().anyMatch(k -> {
try {
byte[] bytes = curator.getData().forPath(keyPath(k));
KeyValue<K, V> keyValue = asObject(bytes);
return keyValue.getValue().equals(value);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
});
} catch (Exception e) {
throw new ZKException(e);
}
}
@Override
public V get(Object key) {
try {
String keyPath = keyPath(key);
if (null == curator.checkExists().forPath(keyPath)) {
return null;
} else {
KeyValue<K, V> keyValue = asObject(curator.getData().forPath(keyPath));
return keyValue.getValue();
}
} catch (Exception e) {
if (!(e instanceof KeeperException.NodeExistsException)) {
throw new ZKException(e);
}
}
return null;
}
@Override
public V put(K key, V value) {
try {
String keyPath = keyPath(key);
KeyValue<K, V> keyValue = new KeyValue<>(key, value);
byte[] valueBytes = asByte(keyValue);
if (get(key) != null) {
curator.setData().forPath(keyPath, valueBytes);
} else {
curator.create().creatingParentsIfNeeded().forPath(keyPath, valueBytes);
}
return value;
} catch (Exception e) {
throw new ZKException(e);
}
}
@Override
public V remove(Object key) {
try {
V result = get(key);
if (result != null) curator.delete().deletingChildrenIfNeeded().forPath(keyPath(key));
return result;
} catch (Exception e) {
throw new ZKException(e);
}
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
m.entrySet().forEach(entry -> put(entry.getKey(), entry.getValue()));
}
@Override
public void clear() {
try {
curator.delete().deletingChildrenIfNeeded().forPath(mapPath);
curator.create().creatingParentsIfNeeded().forPath(mapPath);
} catch (Exception e) {
throw new ZKException(e);
}
}
@Override
public Set<K> keySet() {
try {
return curator.getChildren().forPath(mapPath).stream().map(k -> {
try {
KeyValue<K, V> keyValue = asObject(curator.getData().forPath(keyPath(k)));
return keyValue.getKey();
} catch (Exception ex) {
throw new ZKException(ex);
}
}).collect(Collectors.toSet());
} catch (Exception ex) {
throw new ZKException(ex);
}
}
@Override
public Collection<V> values() {
try {
return curator.getChildren().forPath(mapPath).stream()
.map(k -> {
try {
KeyValue<K, V> keyValue = asObject(curator.getData().forPath(keyPath(k)));
return keyValue.getValue();
} catch (Exception ex) {
throw new ZKException(ex);
}
}
).collect(Collectors.toSet());
} catch (Exception ex) {
throw new ZKException(ex);
}
}
private String keyPath(Object k) {
return mapPath + "/" + k.toString();
}
private String valuePath(Object k, Object v) {
return keyPath(k) + "/" + v.toString();
}
private byte[] asByte(Object object) throws IOException {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
DataOutput dataOutput = new DataOutputStream(byteOut);
dataOutput.writeBoolean(false);
ByteArrayOutputStream javaByteOut = new ByteArrayOutputStream();
ObjectOutput objectOutput = new ObjectOutputStream(javaByteOut);
objectOutput.writeObject(object);
dataOutput.write(javaByteOut.toByteArray());
return byteOut.toByteArray();
}
@SuppressWarnings("unchecked")
private <T> T asObject(byte[] bytes) throws Exception {
ByteArrayInputStream byteIn = new ByteArrayInputStream(bytes);
DataInputStream in = new DataInputStream(byteIn);
byte[] body = new byte[in.available()];
in.readFully(body);
ObjectInputStream objectIn = new ObjectInputStream(new ByteArrayInputStream(body));
return (T) objectIn.readObject();
}
@Override
public Set<Entry<K, V>> entrySet() {
return keySet().stream().map(k -> {
V v = get(k);
return Maps.immutableEntry(k, v);
}).collect(Collectors.toSet());
}
private static class KeyValue<K, V> implements Serializable {
private K key;
private V value;
private KeyValue(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
}