package ZK;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.curator.RetryPolicy;
import com.netflix.curator.retry.RetryOneTime;
import com.netflix.curator.framework.CuratorFramework;
import com.netflix.curator.framework.CuratorFrameworkFactory;
import com.netflix.curator.framework.recipes.locks.InterProcessReadWriteLock;
public class ZKSynch {
//Instantiate the logger
private static final Logger logger = LoggerFactory.getLogger(ZKSynch.class);
//Instantiate the lock container
private static final ConcurrentHashMap<String,InterProcessReadWriteLock> lockStore = new ConcurrentHashMap<String,InterProcessReadWriteLock>();
//Establish a retry policy
//TODO: pick a good retry policy
private static RetryPolicy rtPolicy = new RetryOneTime(1000);
//Namespace prefix for this instance
//TODO: put back for production run
private static String namespace = "EY";//"RACS";
//Pointer to the client instance
private static CuratorFramework client;
//Start the client pointing to the specified server (string of address:port)
public static boolean init(String address){
//Open the new connection to Zookeeper
try{
//client = CuratorFrameworkFactory.newClient(address, rtPolicy);
client = CuratorFrameworkFactory.builder().connectString(address)
.namespace(namespace).retryPolicy(rtPolicy).build();
}catch(IOException e){
logger.error("Failed to create a new ZK client");
client = null;
return false;
}
//Create a new connection state listener
ZKConnectionMgr manager = new ZKConnectionMgr();
//Register the state listener
client.getConnectionStateListenable().addListener(manager);
//Start the client
logger.info("ZK client is starting");
client.start();
//Creation has succeeded
return true;
}
//Stop the client
public static boolean stop(){
if(client != null && client.isStarted()){
//Announce that the client is terminating
logger.info("ZK client is closing");
client.close();
return true;
}else{
return false;
}
}
//Get the static instance of the client
public static CuratorFramework getClient(){
return ZKSynch.client;
}
//Get a reference to the lock
//Needs to be synched in case new lock isn't yet stored
//when another thread asks for the same lock
private static synchronized InterProcessReadWriteLock getLock(String filename){
//Lock container
InterProcessReadWriteLock lock;
//Check if the lock is already created and in the store
//Otherwise generate a new lock and store it
if(lockStore.containsKey(filename)){
lock = lockStore.get(filename);
logger.debug("Reused an old lock");
}else{
lock = new InterProcessReadWriteLock(ZKSynch.client,filename);
lockStore.put(filename, lock);
logger.debug("Made a new lock");
}
//Return the pointer to the lock
return lock;
}
//Lock operations on the ZK reader/writer locks
private static boolean lockOp(String filename, char type, char op){
//Null check and make sure client is running
if(filename == null || !ZKSynch.client.isStarted()){
return false;
}
logger.info(op+" a "+type+" lock for /"+filename);
//Sanitize the filename
filename = "/" + filename;
//Get the lock
InterProcessReadWriteLock lock = getLock(filename);
//Acquire/release the read/write lock based on parameters
try{
if(type == 'R'){
if(op == 'A'){
lock.readLock().acquire();
}else{
lock.readLock().release();
}
}else{
if(op == 'A'){
lock.writeLock().acquire();
}else{
lock.writeLock().release();
}
}
}catch(Exception e){
logger.error("Could not {} the specified {} lock",type,op);
return false;
}
//If gotten to this point, return true
return true;
}
//Release the lock on a selected filename
public static boolean acquireReader(String filename){
return lockOp(filename,'R','A');
}
//Release the lock on a selected filename
public static boolean releaseReader(String filename){
return lockOp(filename,'R','R');
}
//Release the lock on a selected filename
public static boolean acquireWriter(String filename){
return lockOp(filename,'W','A');
}
//Release the lock on a selected filename
public static boolean releaseWriter(String filename){
return lockOp(filename,'W','R');
}
//Create parent nodes if they don't already exist
private static boolean createParentNodes(String[] nodes){
//Sanity check
if(nodes == null || !ZKSynch.client.isStarted()){
return false;
}
//For every parent node, if it doesn't exist, create it
try{
String path = new String();
for(int i = 0; i < nodes.length-1; i++){
path += "/" + nodes[i];
if(client.checkExists().forPath(path) == null){
client.create().forPath(path);
}
}
} catch (Exception e){
logger.error("Could not create the parent nodes");
return false;
}
return true;
}
//Store the metadata
public static boolean putData(String filename, byte[] data){
//Null check and make sure client is running
if(filename == null || !ZKSynch.client.isStarted()){
return false;
}
//If the node exists, store the data
//Otherwise create a new node
try{
if(client.checkExists().forPath("/"+filename) != null){
client.setData().forPath("/"+filename,data);
}else{
//Create the parent nodes if they don't exist
String [] nodes = filename.split("/");
if(createParentNodes(nodes)){
//Create the node itself
client.create().forPath("/"+filename,data);
}else{
return false;
}
}
logger.info("Wrote {}",filename);
}catch(Exception e){
logger.error("Could not write {}",filename);
return false;
}
return true;
}
//Put data in string form
public static boolean putDataS(String filename, String data){
return putData(filename,data.getBytes());
}
//Put data as an integer tuple
//[0] = version, [1] = file size
public static boolean putDataI(String filename, int version, int filesize){
String data = String.format("%d_%d",version,filesize);
return putData(filename,data.getBytes());
}
//Get the metadata from the specified node
public static byte[] getData(String filename){
//Null and connection check
if(filename == null || !ZKSynch.client.isStarted()){
return null;
}
//Sanitize the filename
filename = "/" + filename;
try{
if(client.checkExists().forPath(filename) != null){
return client.getData().forPath(filename);
}else{
return null;
}
}catch(Exception e){
logger.error("Could not get data from {}",filename);
return null;
}
}
//Return the data in string form
public static String getDataS(String filename){
byte[] data = getData(filename);
if(data != null && data.length > 0){
return new String(data);
}else{
return null;
}
}
//Return the data as array of integers
//[0] = version, [1] = file size
public static int[] getDataI(String filename){
byte[] data = getData(filename);
if(data != null && data.length > 0){
String[] d = new String(data).split("_");
if(d.length < 2){
return null;
}
int[] out = new int[2];
out[0] = Integer.parseInt(d[0]);
out[1] = Integer.parseInt(d[1]);
return out;
}else{
return null;
}
}
//Get a directory listing by fetching the children
public static String[] getListing(String dirname){
String[] out;
//Null and connection check
if(dirname == null || !ZKSynch.client.isStarted()){
return null;
}
try{
//Fetch the list of children
List<String> children = client.getChildren().forPath(dirname.length() == 0 ? dirname : "/" + dirname);
logger.info("The node {} has {} children",(dirname.length() == 0 ?"/":dirname),children.size());
out = new String[children.size()];
//Get the contents of
int ctr = 0;
for(String child : children){
int[] data = getDataI(dirname.length() == 0 ? child : dirname + "/" + child);
out[ctr] = child + "_" + (data != null ? data[1] : "dir");
ctr++;
}
return out;
} catch (Exception e){
logger.error("Could not fetch the directory listing for "+dirname);
return null;
}
}
public static ArrayList<String> getAllListing(){
ArrayList<String> out = new ArrayList<String>();
//Connection check
if(!ZKSynch.client.isStarted()){
return null;
}
//Start at the root
try{
List<String> children = client.getChildren().forPath("");
logger.info("/ has {} children",children.size());
for(String child : children){
int[] data = getDataI(child);
if (data != null){
out.add(child+"_"+data[1]);
} else {
out.addAll(getAllListingRec(child));
}
}
return out;
} catch (Exception e){
logger.error("Failed to fetch complete directory listing");
logger.error(e.getMessage());
return null;
}
}
private static ArrayList<String> getAllListingRec(String path) throws Exception{
ArrayList<String> out = new ArrayList<String>();
List<String> children = client.getChildren().forPath("/" + path);
logger.info("/"+path+" has {} children", children.size());
for(String child : children){
int[] data = getDataI(path + "/" + child);
if(data != null){
out.add(path + "/" + child + "_" + data[1]);
} else {
out.addAll(getAllListingRec(path + "/" + child));
}
}
return out;
}
//Delete the specified node
public static boolean delData(String filename){
//Null and connection check
if(filename == null || !ZKSynch.client.isStarted()){
return false;
}
//Sanitize the filename
filename = "/" + filename;
//Issue the delete command
try{
client.delete().forPath(filename);
logger.info("Deleted {}",filename);
return true;
} catch (Exception e){
logger.error("Could not delete {}",filename);
return false;
}
}
//Delete the node and all of its children
public static boolean purge(String filename){
//Null and connection check
if(filename == null || !ZKSynch.client.isStarted()){
return false;
}
try{
//Fetch the list of children
List<String> children = client.getChildren().forPath("/"+filename);
//If children exist, recursively delete them
if(!children.isEmpty()){
for(String child : children){
purge(filename+"/"+child);
}
}
//Delete the node itself
client.delete().forPath("/"+filename);
logger.info("Deleted /{}",filename);
} catch (Exception e){
logger.error("Could not purge /{}",filename);
return false;
}
return true;
}
}