package com.neverwinterdp.registry.zk;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.PreDestroy;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZKUtil;
import org.apache.zookeeper.ZooDefs.Perms;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.common.PathUtils;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.neverwinterdp.registry.DataMapperCallback;
import com.neverwinterdp.registry.ErrorCode;
import com.neverwinterdp.registry.Node;
import com.neverwinterdp.registry.NodeCreateMode;
import com.neverwinterdp.registry.RefNode;
import com.neverwinterdp.registry.Registry;
import com.neverwinterdp.registry.RegistryConfig;
import com.neverwinterdp.registry.RegistryException;
import com.neverwinterdp.registry.Transaction;
import com.neverwinterdp.registry.BatchOperations;
import com.neverwinterdp.registry.event.NodeWatcher;
import com.neverwinterdp.util.JSONSerializer;
@Singleton
public class RegistryImpl implements Registry {
static public final Id ANYONE_ID = new Id("world", "anyone");
static public final ArrayList<ACL> DEFAULT_ACL = new ArrayList<ACL>(Collections.singletonList(new ACL(Perms.ALL, ANYONE_ID)));
@Inject
private RegistryConfig config;
private ZooKeeper zkClient ;
public RegistryImpl() {
}
public RegistryImpl(RegistryConfig config) {
this.config = config;
}
public RegistryConfig getRegistryConfig() {
return this.config ;
}
public ZooKeeper getZkClient() { return this.zkClient ; }
public String getSessionId() {
if(zkClient == null) return null ;
return Long.toString(zkClient.getSessionId()) ;
}
@Inject
public void init() throws RegistryException {
connect();
}
@Override
public Registry connect() throws RegistryException {
return connect(5000) ;
}
@Override
public Registry connect(long timeout) throws RegistryException {
try {
zkClient = new ZooKeeper(config.getConnect(), 15000, new RegistryWatcher());
} catch (IOException ex) {
throw new RegistryException(ErrorCode.Connection, ex) ;
}
long waitTime = 0 ;
while(waitTime < timeout) {
if(zkClient.getState().isConnected()) {
zkCreateIfNotExist(config.getDbDomain()) ;
return this;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RegistryException(ErrorCode.Connection, "Cannot connect due to the interrupt") ;
}
waitTime += 500;
}
try {
zkClient.close();
} catch (InterruptedException e) {
throw new RegistryException(ErrorCode.Connection, "Cannot connect after " + timeout + "ms") ;
}
zkClient = null ;
throw new RegistryException(ErrorCode.Connection, "Cannot connect after " + timeout + "ms") ;
}
@Override
public void disconnect() throws RegistryException {
if(zkClient != null) {
try {
zkClient.close();;
zkClient = null ;
} catch (InterruptedException e) {
throw new RegistryException(ErrorCode.Connection, e) ;
}
}
}
public void checkConnected() throws RegistryException {
if(!isConnect()) {
throw new RegistryException(ErrorCode.Connection, "Not connected to a zookeeper server") ;
}
}
@Override
public boolean isConnect() { return zkClient != null && zkClient.getState().isConnected(); }
@Override
public Node create(String path, NodeCreateMode mode) throws RegistryException {
checkConnected();
return create(path, new byte[0], mode);
}
@Override
public void createRef(String path, String toPath, NodeCreateMode mode) throws RegistryException {
checkConnected();
RefNode refNode = new RefNode();
refNode.setPath(toPath);
create(path, refNode, mode);
}
@Override
public Node create(String path, byte[] data, NodeCreateMode mode) throws RegistryException {
checkConnected();
try {
String retPath = zkClient.create(realPath(path), data, DEFAULT_ACL, toCreateMode(mode)) ;
if(mode == NodeCreateMode.PERSISTENT_SEQUENTIAL || mode == NodeCreateMode.EPHEMERAL_SEQUENTIAL) {
path = retPath.substring(config.getDbDomain().length()) ;
}
return new Node(this, path);
} catch (KeeperException | InterruptedException e) {
throw new RegistryException(ErrorCode.NodeCreation, e) ;
}
}
@Override
public <T> Node create(String path, T data, NodeCreateMode mode) throws RegistryException {
checkConnected();
byte[] bytes = JSONSerializer.INSTANCE.toBytes(data);
return create(path, bytes, mode);
}
@Override
public Node createIfNotExist(String path) throws RegistryException {
checkConnected();
zkCreateIfNotExist(realPath(path));
return new Node(this, path) ;
}
@Override
public Node get(String path) throws RegistryException {
checkConnected();
return new Node(this, path) ;
}
@Override
public Node getRef(String path) throws RegistryException {
checkConnected();
RefNode refNode = getDataAs(path, RefNode.class);
return new Node(this, refNode.getPath()) ;
}
@Override
public byte[] getData(String path) throws RegistryException {
checkConnected();
try {
return zkClient.getData(realPath(path), null, new Stat()) ;
} catch (KeeperException | InterruptedException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
@Override
public <T> T getDataAs(String path, Class<T> type) throws RegistryException {
checkConnected();
try {
byte[] bytes = zkClient.getData(realPath(path), null, new Stat()) ;
if(bytes == null || bytes.length == 0) return null;
return JSONSerializer.INSTANCE.fromBytes(bytes, type);
} catch (KeeperException | InterruptedException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
public <T> T getDataAs(String path, Class<T> type, DataMapperCallback<T> mapper) throws RegistryException {
checkConnected();
try {
byte[] bytes = zkClient.getData(realPath(path), null, new Stat()) ;
if(bytes == null || bytes.length == 0) return null;
return mapper.map(path, bytes, type);
} catch (KeeperException | InterruptedException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
@Override
public <T> List<T> getDataAs(List<String> paths, Class<T> type) throws RegistryException {
checkConnected();
List<T> holder = new ArrayList<T>();
for(String path : paths) {
holder.add(getDataAs(path, type));
}
return holder;
}
@Override
public <T> List<T> getDataAs(List<String> paths, Class<T> type, DataMapperCallback<T> mapper) throws RegistryException {
checkConnected();
List<T> holder = new ArrayList<T>();
for(String path : paths) {
holder.add(getDataAs(path, type, mapper));
}
return holder;
}
public void setData(String path, byte[] data) throws RegistryException {
checkConnected();
try {
Stat stat = zkClient.setData(realPath(path), data, -1) ;
} catch (KeeperException | InterruptedException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
public <T> void setData(String path, T data) throws RegistryException {
checkConnected();
byte[] bytes = JSONSerializer.INSTANCE.toBytes(data) ;
setData(path, bytes);
}
public List<String> getChildren(String path) throws RegistryException {
checkConnected();
try {
List<String> names = zkClient.getChildren(realPath(path), false);
return names ;
} catch (KeeperException | InterruptedException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
public List<String> getChildrenPath(String path) throws RegistryException {
checkConnected();
try {
List<String> names = zkClient.getChildren(realPath(path), false);
List<String> paths = new ArrayList<String>() ;
for(String name : names) {
paths.add(path + "/" + name);
}
return names ;
} catch (KeeperException | InterruptedException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
public List<String> getChildren(String path, boolean watch) throws RegistryException {
checkConnected();
try {
List<String> names = zkClient.getChildren(realPath(path), watch);
return names ;
} catch (KeeperException | InterruptedException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
public <T> List<T> getChildrenAs(String path, Class<T> type) throws RegistryException {
checkConnected();
List<T> holder = new ArrayList<T>();
List<String> nodes = getChildren(path);
Collections.sort(nodes);
for(int i = 0; i < nodes.size(); i++) {
String name = nodes.get(i) ;
T object = getDataAs(path + "/" + name, type);
holder.add(object);
}
return holder ;
}
@Override
public <T> List<T> getChildrenAs(String path, Class<T> type, DataMapperCallback<T> callback) throws RegistryException {
checkConnected();
List<T> holder = new ArrayList<T>();
List<String> nodes = getChildren(path);
Collections.sort(nodes);
for(int i = 0; i < nodes.size(); i++) {
String name = nodes.get(i) ;
T object = getDataAs(path + "/" + name, type, callback);
holder.add(object);
}
return holder ;
}
@Override
public <T> List<T> getRefChildrenAs(String path, Class<T> type) throws RegistryException {
checkConnected();
List<RefNode> refNodes = getChildrenAs(path, RefNode.class) ;
List<String> paths = new ArrayList<>() ;
for(int i = 0; i < refNodes.size(); i++) {
paths.add(refNodes.get(i).getPath());
}
return getDataAs(paths, type);
}
@Override
public boolean exists(String path) throws RegistryException {
checkConnected();
try {
Stat stat = zkClient.exists(realPath(path), false) ;
if(stat != null) return true ;
return false ;
} catch (KeeperException | InterruptedException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
@Override
public void watchModify(String path, NodeWatcher watcher) throws RegistryException {
checkConnected();
try {
zkClient.getData(realPath(path), new ZKNodeWatcher(config.getDbDomain(), watcher), new Stat()) ;
} catch (InterruptedException | KeeperException e) {
throw toRegistryException("Cannot watch the node " + path, e) ;
}
}
@Override
public void watchExists(String path, NodeWatcher watcher) throws RegistryException {
checkConnected();
try {
zkClient.exists(realPath(path), new ZKNodeWatcher(config.getDbDomain(), watcher)) ;
} catch (InterruptedException | KeeperException e) {
throw toRegistryException("Cannot watch the node " + path, e) ;
}
}
@Override
public void watchChildren(String path, NodeWatcher watcher) throws RegistryException {
checkConnected();
try {
List<String> names = zkClient.getChildren(realPath(path), new ZKNodeWatcher(config.getDbDomain(), watcher)) ;
} catch (KeeperException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
} catch (InterruptedException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
@Override
public void delete(String path) throws RegistryException {
checkConnected();
try {
zkClient.delete(realPath(path), -1);
} catch (InterruptedException | KeeperException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
@Override
public void rdelete(String path) throws RegistryException {
checkConnected();
try {
PathUtils.validatePath(path);
List<String> tree = ZKUtil.listSubTreeBFS(zkClient, realPath(path));
for (int i = tree.size() - 1; i >= 0 ; --i) {
//Delete the leaves first and eventually get rid of the root
zkClient.delete(tree.get(i), -1); //Delete all versions of the node with -1.
}
} catch (InterruptedException | KeeperException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
public List<String> findDencendantPaths(String path) throws RegistryException {
List<String> paths = findDencendantRealPaths(path) ;
List<String> holder = new ArrayList<String>() ;
for(int i = 0; i < paths.size(); i++) {
String selPath = paths.get(i) ;
selPath = selPath.substring(config.getDbDomain().length());
holder.add(selPath);
}
return holder ;
}
public List<String> findDencendantRealPaths(String path) throws RegistryException {
checkConnected();
try {
PathUtils.validatePath(realPath(path));
return ZKUtil.listSubTreeBFS(zkClient, realPath(path));
} catch (InterruptedException | KeeperException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
public void rcopy(String path, String toPath) throws RegistryException {
try {
PathUtils.validatePath(path);
List<String> tree = ZKUtil.listSubTreeBFS(zkClient, realPath(path));
for (int i = 0; i < tree.size(); i++) {
String selPath = tree.get(i);
String selToPath = selPath.replace(path, toPath);
byte[] data = zkClient.getData(selPath, false, new Stat()) ;
zkClient.create(selToPath, data, DEFAULT_ACL, toCreateMode(NodeCreateMode.PERSISTENT)) ;
}
} catch (InterruptedException | KeeperException e) {
throw new RegistryException(ErrorCode.Unknown, e) ;
}
}
@Override
public Transaction getTransaction() {
return new TransactionImpl(this, zkClient.transaction());
}
public <T> T executeBatch(BatchOperations<T> ops, int retry, long timeoutThreshold) throws RegistryException {
for(int i = 0;i < retry; i++) {
try {
T result = ops.execute(this);
return result;
} catch (RegistryException e) {
if(e.getErrorCode() != ErrorCode.Timeout) throw e;
} catch (Exception e) {
throw new RegistryException(ErrorCode.Unknown, e);
}
}
throw new RegistryException(ErrorCode.Unknown, "Fail after " + retry + "tries");
}
@Override
public Registry newRegistry() throws RegistryException {
return new RegistryImpl(config);
}
private void zkCreateIfNotExist(String path) throws RegistryException {
checkConnected();
try {
if (zkClient.exists(path, false) != null) new Node(this, path);
StringBuilder pathB = new StringBuilder();
String[] pathParts = path.split("/");
for(String pathEle : pathParts) {
if(pathEle.length() == 0) continue ; //root
pathB.append("/").append(pathEle);
String pathString = pathB.toString();
//bother with the exists call or not?
Stat nodeStat = zkClient.exists(pathString, false);
if (nodeStat == null) {
try {
zkClient.create(pathString, null, DEFAULT_ACL, CreateMode.PERSISTENT);
} catch(KeeperException.NodeExistsException ex) {
break;
}
}
}
} catch(Exception ex) {
throw new RegistryException(ErrorCode.NodeCreation, ex) ;
}
}
static CreateMode toCreateMode(NodeCreateMode mode) {
if(mode == NodeCreateMode.PERSISTENT) return CreateMode.PERSISTENT ;
else if(mode == NodeCreateMode.PERSISTENT_SEQUENTIAL) return CreateMode.PERSISTENT_SEQUENTIAL ;
else if(mode == NodeCreateMode.EPHEMERAL) return CreateMode.EPHEMERAL ;
else if(mode == NodeCreateMode.EPHEMERAL_SEQUENTIAL) return CreateMode.EPHEMERAL_SEQUENTIAL ;
throw new RuntimeException("Mode " + mode + " is not supported") ;
}
String realPath(String path) {
if(path.equals("/")) return config.getDbDomain() ;
return config.getDbDomain() + path;
}
private RegistryException toRegistryException(String message, Throwable t) {
if(t instanceof InterruptedException) {
return new RegistryException(ErrorCode.Timeout, message, t) ;
} else if(t instanceof KeeperException) {
KeeperException kEx = (KeeperException) t;
if(kEx.code() == KeeperException.Code.NONODE) {
return new RegistryException(ErrorCode.NoNode, message, t) ;
}
}
return new RegistryException(ErrorCode.Unknown, message, t) ;
}
@PreDestroy
public void onDestroy() throws RegistryException {
disconnect();
}
}