package com.hubspot.baragon.data;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.PathAndBytesable;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.io.BaseEncoding;
import com.hubspot.baragon.config.ZooKeeperConfiguration;
import com.hubspot.baragon.utils.JavaUtils;
// because curator is a piece of shit
public abstract class AbstractDataStore {
private static final Logger LOG = LoggerFactory.getLogger(AbstractDataStore.class);
public enum OperationType {
READ,
WRITE;
}
protected final CuratorFramework curatorFramework;
protected final ObjectMapper objectMapper;
protected final ZooKeeperConfiguration zooKeeperConfiguration;
public static final Comparator<String> SEQUENCE_NODE_COMPARATOR_LOW_TO_HIGH = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.substring(o1.length()-10).compareTo(o2.substring(o2.length()-10));
}
};
public static final Comparator<String> SEQUENCE_NODE_COMPARATOR_HIGH_TO_LOW = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.substring(o2.length()-10).compareTo(o1.substring(o1.length()-10));
}
};
public AbstractDataStore(CuratorFramework curatorFramework, ObjectMapper objectMapper, ZooKeeperConfiguration zooKeeperConfiguration) {
this.curatorFramework = curatorFramework;
this.objectMapper = objectMapper;
this.zooKeeperConfiguration = zooKeeperConfiguration;
}
protected void log(OperationType type, Optional<Integer> numItems, Optional<Integer> bytes, long start, String path) {
final String message = String.format("%s (items: %s) (bytes: %s) in %s (%s)", type, numItems.or(1), bytes.or(0), JavaUtils.duration(start), path);
final long duration = System.currentTimeMillis() - start;
if ((bytes.isPresent() && bytes.get() > zooKeeperConfiguration.getDebugCuratorCallOverBytes()) || (duration > zooKeeperConfiguration.getDebugCuratorCallOverMillis())) {
LOG.debug(message);
} else {
LOG.trace(message);
}
}
protected String encodeUrl(String url) {
return BaseEncoding.base64Url().encode(url.getBytes(Charsets.UTF_8));
}
protected String decodeUrl(String encodedUrl) {
return new String(BaseEncoding.base64Url().decode(encodedUrl), Charsets.UTF_8);
}
protected String sanitizeNodeName(String name) {
return name.contains("/") ? encodeUrl(name) : name;
}
protected boolean nodeExists(String path) {
final long start = System.currentTimeMillis();
try {
Stat stat = curatorFramework.checkExists().forPath(path);
log(OperationType.READ, Optional.<Integer>absent(), Optional.<Integer>absent(), start, path);
return stat != null;
} catch (KeeperException.NoNodeException e) {
return false;
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
protected <T> void writeToZk(String path, T data) {
final long start = System.currentTimeMillis();
try {
final byte[] serializedInfo = serialize(data);
final PathAndBytesable<?> builder;
if (curatorFramework.checkExists().forPath(path) != null) {
builder = curatorFramework.setData();
} else {
builder = curatorFramework.create().creatingParentsIfNeeded();
}
builder.forPath(path, serializedInfo);
log(OperationType.WRITE, Optional.<Integer>absent(), Optional.of(serializedInfo.length), start, path);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
protected <T> byte[] serialize(T data) {
try {
return objectMapper.writeValueAsBytes(data);
} catch (JsonProcessingException e) {
throw Throwables.propagate(e);
}
}
protected <T> Optional<T> readFromZk(final String path, final Class<T> klass) {
final long start = System.currentTimeMillis();
Optional<byte[]> data = readFromZk(path);
if (data.isPresent()) {
log(OperationType.READ, Optional.<Integer>absent(), Optional.of(data.get().length), start, path);
if (data.get().length > 0) {
return Optional.of(deserialize(data.get(), klass, path));
}
}
return Optional.absent();
}
protected Optional<byte[]> readFromZk(String path) {
try {
byte[] data = curatorFramework.getData().forPath(path);
if (data.length > 0) {
return Optional.of(curatorFramework.getData().forPath(path));
} else {
return Optional.absent();
}
} catch (KeeperException.NoNodeException nne) {
return Optional.absent();
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
protected <T> T deserialize(byte[] data, Class<T> klass, String path) {
try {
return objectMapper.readValue(data, klass);
} catch (JsonParseException jpe) {
try {
LOG.error("Invalid Json at path {}: {}", path, new String(data, StandardCharsets.UTF_8), jpe);
} catch (Exception e) {
LOG.error("Could not get raw json string at path {}", path, e);
}
throw Throwables.propagate(jpe);
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
protected String createNode(String path) {
final long start = System.currentTimeMillis();
try {
final String result = curatorFramework.create().creatingParentsIfNeeded().forPath(path);
log(OperationType.WRITE, Optional.<Integer>absent(), Optional.<Integer>absent(), start, path);
return result;
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
protected String createPersistentSequentialNode(String path) {
final long start = System.currentTimeMillis();
try {
final String result = curatorFramework.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath(path);
log(OperationType.WRITE, Optional.<Integer>absent(), Optional.<Integer>absent(), start, path);
return result;
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
protected boolean deleteNode(String path) {
return deleteNode(path, false);
}
protected boolean deleteNode(String path, boolean recursive) {
final long start = System.currentTimeMillis();
try {
if (recursive) {
curatorFramework.delete().deletingChildrenIfNeeded().forPath(path);
log(OperationType.WRITE, Optional.<Integer>absent(), Optional.<Integer>absent(), start, path);
} else {
curatorFramework.delete().forPath(path);
log(OperationType.WRITE, Optional.<Integer>absent(), Optional.<Integer>absent(), start, path);
}
return true;
} catch (KeeperException.NoNodeException e) {
return false;
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
protected List<String> getChildren(String path) {
final long start = System.currentTimeMillis();
try {
List<String> children = curatorFramework.getChildren().forPath(path);
log(OperationType.READ, Optional.of(children.size()), Optional.<Integer>absent(), start, path);
return children;
} catch (KeeperException.NoNodeException e) {
return Collections.emptyList();
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
protected Optional<Long> getUpdatedAt(String path) {
final long start = System.currentTimeMillis();
try {
Stat stat = curatorFramework.checkExists().forPath(path);
log(OperationType.READ, Optional.<Integer>absent(), Optional.<Integer>absent(), start, path);
if (stat != null) {
return Optional.of(stat.getMtime());
} else {
return Optional.absent();
}
} catch (KeeperException.NoNodeException e) {
return Optional.absent();
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
}