package edu.uw.zookeeper.data;
import static com.google.common.base.Preconditions.*;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.engio.mbassy.common.IConcurrentSet;
import net.engio.mbassy.common.StrongConcurrentSet;
import org.apache.jute.Record;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import edu.uw.zookeeper.client.ClientExecutor;
import edu.uw.zookeeper.common.Eventful;
import edu.uw.zookeeper.common.LoggingFutureListener;
import edu.uw.zookeeper.common.Promise;
import edu.uw.zookeeper.common.SettableFuturePromise;
import edu.uw.zookeeper.data.SimpleLabelTrie;
import edu.uw.zookeeper.data.ZNodeLabelVector;
import edu.uw.zookeeper.data.ZNodeName;
import edu.uw.zookeeper.data.NameTrie;
import edu.uw.zookeeper.data.ZNodeLabel;
import edu.uw.zookeeper.protocol.Operation;
import edu.uw.zookeeper.protocol.ProtocolResponseMessage;
import edu.uw.zookeeper.protocol.SessionListener;
import edu.uw.zookeeper.protocol.ZxidReference;
import edu.uw.zookeeper.protocol.client.ZxidTracker;
import edu.uw.zookeeper.protocol.proto.IMultiRequest;
import edu.uw.zookeeper.protocol.proto.IMultiResponse;
import edu.uw.zookeeper.protocol.proto.IStat;
import edu.uw.zookeeper.protocol.proto.Records;
import edu.uw.zookeeper.protocol.proto.Records.MultiOpRequest;
import edu.uw.zookeeper.protocol.proto.Records.MultiOpResponse;
/**
* Only caches the results of operations submitted through this wrapper.
*
* Not thread-safe.
*/
public class ZNodeCache<E extends AbstractNameTrie.SimpleNode<E> & ZNodeCache.CacheNode<E,?>, I extends Operation.Request, O extends Operation.ProtocolResponse<?>>
implements ClientExecutor<I, O, SessionListener> {
public static <I extends Operation.Request,O extends Operation.ProtocolResponse<?>> ZNodeCache<SimpleCacheNode,I,O> newInstance(
ClientExecutor<? super I,O,SessionListener> client) {
return newInstance(client, SimpleCacheNode.root());
}
public static <E extends AbstractNameTrie.SimpleNode<E> & ZNodeCache.CacheNode<E,?>,I extends Operation.Request, O extends Operation.ProtocolResponse<?>> ZNodeCache<E,I,O> newInstance(
ClientExecutor<? super I,O,SessionListener> client, E root) {
return new ZNodeCache<E,I,O>(client, new CacheEvents(new StrongConcurrentSet<CacheListener>()), SimpleLabelTrie.forRoot(root));
}
public static interface CacheListener {
void handleCacheEvent(Set<NodeWatchEvent> events);
}
public static interface CacheNode<E extends CacheNode<E,?>, V> extends DefaultsNode<E> {
long stamp();
long touch(long zxid);
List<NodeWatchEvent> update(long zxid, Record...records);
StampedValue<Records.ZNodeStatGetter> stat();
StampedValue<V> data();
}
public static class CacheEvents implements Eventful<CacheListener>, CacheListener {
protected final IConcurrentSet<CacheListener> listeners;
public CacheEvents(IConcurrentSet<CacheListener> listeners) {
this.listeners = listeners;
}
@Override
public void subscribe(CacheListener listener) {
listeners.add(listener);
}
@Override
public boolean unsubscribe(CacheListener listener) {
return listeners.remove(listener);
}
@Override
public void handleCacheEvent(Set<NodeWatchEvent> events) {
if (! events.isEmpty()) {
for (CacheListener listener: listeners) {
listener.handleCacheEvent(events);
}
}
}
}
public static abstract class AbstractCacheNode<E extends AbstractCacheNode<E,?>, V> extends DefaultsNode.AbstractDefaultsNode<E> implements CacheNode<E,V> {
protected long stamp;
protected StampedValue<Records.ZNodeStatGetter> stat;
protected StampedValue<V> data;
protected AbstractCacheNode(
NameTrie.Pointer<? extends E> parent) {
this(null, null, -1L, parent);
}
protected AbstractCacheNode(
V data,
Records.ZNodeStatGetter stat,
long stamp,
NameTrie.Pointer<? extends E> parent) {
this(data, stat, stamp, parent, Maps.<ZNodeName, E>newHashMap());
}
protected AbstractCacheNode(
V data,
Records.ZNodeStatGetter stat,
long stamp,
NameTrie.Pointer<? extends E> parent,
Map<ZNodeName, E> children) {
super(SimpleLabelTrie.pathOf(parent), parent, children);
this.stat = StampedValue.valueOf(stamp, stat);
this.data = StampedValue.valueOf(stamp, data);
this.stamp = stamp;
}
@Override
public long stamp() {
return stamp;
}
@Override
public long touch(long stamp) {
if (this.stamp < stamp) {
long prev = this.stamp;
this.stamp = stamp;
return prev;
} else {
return this.stamp;
}
}
@Override
public StampedValue<Records.ZNodeStatGetter> stat() {
return stat;
}
@Override
public StampedValue<V> data() {
return data;
}
@Override
public List<NodeWatchEvent> update(long zxid, Record...records) {
final StampedValue<Records.ZNodeStatGetter> prevStat = stat;
final StampedValue<V> prevData = data;
touch(zxid);
for (Record record: records) {
if (record instanceof Records.StatGetter) {
if (zxid > stat.stamp()) {
final Records.ZNodeStatGetter prev = stat.get();
final Records.ZNodeStatGetter updated = new IStat(((Records.StatGetter) record).getStat());
stat = StampedValue.valueOf(zxid, Objects.equal(prev, updated) ? prev : updated);
}
}
if (record instanceof Records.DataGetter) {
if (zxid > data.stamp()) {
final V prev = data.get();
final V updated = transformData(((Records.DataGetter) record).getData());
data = StampedValue.valueOf(zxid, equivalentData(prev, updated) ? prev : updated);
}
}
}
final ImmutableList<NodeWatchEvent> events;
if (((data != prevData) && (prevData.get() != data.get())) ||
((prevStat != stat) && (prevStat.get() != stat.get()) &&
((prevStat.get() == null) || (prevStat.get().getVersion() < stat.get().getVersion())))) {
events = ImmutableList.of(NodeWatchEvent.nodeDataChanged(path()));
} else {
events = ImmutableList.of();
}
return events;
}
protected abstract V transformData(byte[] data);
protected abstract boolean equivalentData(V v1, V v2);
@Override
public String toString() {
return MoreObjects.toStringHelper("")
.add("path", path())
.add("children", keySet())
.add("stamp", stamp())
.add("stat", stat())
.add("data", data()).toString();
}
}
public static class SimpleCacheNode extends AbstractCacheNode<SimpleCacheNode, byte[]> {
public static SimpleCacheNode root() {
return new SimpleCacheNode(SimpleLabelTrie.<SimpleCacheNode>rootPointer());
}
protected SimpleCacheNode(
NameTrie.Pointer<? extends SimpleCacheNode> parent) {
super(parent);
}
protected SimpleCacheNode(
byte[] data,
Records.ZNodeStatGetter stat,
long stamp,
NameTrie.Pointer<? extends SimpleCacheNode> parent) {
super(data, stat, stamp, parent);
}
@Override
protected byte[] transformData(byte[] data) {
return data;
}
@Override
protected boolean equivalentData(byte[] v1, byte[] v2) {
return Arrays.equals(v1, v2);
}
@Override
protected SimpleCacheNode newChild(ZNodeName label) {
NameTrie.Pointer<SimpleCacheNode> pointer = SimpleLabelTrie.weakPointer(label, this);
return new SimpleCacheNode(pointer);
}
}
// to apply changes to the cache before listeners are notified
protected class CachingCallback implements Function<O,O> {
protected final I request;
public CachingCallback(I request) {
this.request = request;
}
@Override
public O apply(O input) {
Records.Request request = (Records.Request)
((this.request instanceof Records.Request) ?
this.request :
((Operation.RecordHolder<?>) this.request).record());
handleResult(request, input);
return input;
}
}
protected final Logger logger;
protected final ZxidTracker lastZxid;
protected final ClientExecutor<? super I, O, SessionListener> client;
protected final NameTrie<E> trie;
protected final CacheEvents events;
protected ZNodeCache(
ClientExecutor<? super I, O, SessionListener> client,
CacheEvents events,
NameTrie<E> trie) {
this.logger = LogManager.getLogger(getClass());
this.client = checkNotNull(client);
this.events = checkNotNull(events);
this.lastZxid = ZxidTracker.zero();
this.trie = checkNotNull(trie);
}
public NameTrie<E> cache() {
return trie;
}
public CacheEvents events() {
return events;
}
public ZxidReference lastZxid() {
return lastZxid;
}
public ClientExecutor<? super I, O, SessionListener> client() {
return client;
}
@Override
public void subscribe(SessionListener listener) {
client.subscribe(listener);
}
@Override
public boolean unsubscribe(SessionListener listener) {
return client.unsubscribe(listener);
}
@Override
public ListenableFuture<O> submit(I request) {
return submit(request, SettableFuturePromise.<O>create());
}
@Override
public ListenableFuture<O> submit(I request, Promise<O> promise) {
ListenableFuture<O> future = Futures.transform(
client.submit(request, promise),
new CachingCallback(request));
LoggingFutureListener.listen(logger, future);
return future;
}
protected void handleResult(Records.Request request, Operation.ProtocolResponse<?> result) {
ImmutableSet.Builder<NodeWatchEvent> events = ImmutableSet.builder();
handleResult(request, result, events);
events().handleCacheEvent(events.build());
}
protected void handleResult(Records.Request request, Operation.ProtocolResponse<?> result, ImmutableSet.Builder<NodeWatchEvent> events) {
long zxid = result.zxid();
lastZxid.update(zxid);
Records.Response response = result.record();
if (response instanceof Operation.Error) {
switch (((Operation.Error) response).error()) {
case NONODE:
{
ZNodePath path = ZNodePath.fromString(((Records.PathGetter) request).getPath());
switch (request.opcode()) {
case CREATE:
case CREATE2:
{
path = ((AbsoluteZNodePath) path).parent();
}
case CHECK:
case DELETE:
case EXISTS:
case GET_ACL:
case GET_CHILDREN:
case GET_CHILDREN2:
case GET_DATA:
case SET_ACL:
case SET_DATA:
{
remove(path, zxid, events);
return;
}
default:
break;
}
break;
}
case NODEEXISTS:
{
ZNodePath path = (ZNodePath) ZNodeLabelVector.fromString(((Records.PathGetter) request).getPath());
add(path, zxid, events);
break;
}
default:
break;
}
} else {
switch (response.opcode()) {
case CREATE:
case CREATE2:
{
ZNodePath path = (ZNodePath) ZNodeLabelVector.fromString(((Records.PathGetter) response).getPath());
update(path, zxid, events, request, response);
break;
}
case DELETE:
{
ZNodePath path = (ZNodePath) ZNodeLabelVector.fromString(((Records.PathGetter) request).getPath());
remove(path, zxid, events);
break;
}
case CHECK:
case EXISTS:
case GET_ACL:
case SET_ACL:
{
ZNodePath path = (ZNodePath) ZNodeLabelVector.fromString(((Records.PathGetter) request).getPath());
update(path, zxid, events, response);
break;
}
case GET_CHILDREN:
case GET_CHILDREN2:
{
ZNodePath path = (ZNodePath) ZNodeLabelVector.fromString(((Records.PathGetter) request).getPath());
List<String> children = ((Records.ChildrenGetter) response).getChildren();
E node = add(path, zxid, events);
Iterator<Map.Entry<ZNodeName, E>> entries = node.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<ZNodeName, E> entry = entries.next();
if (!children.contains(entry.getKey().toString())) {
E child = entry.getValue();
if (child.stamp() < zxid) {
entries.remove();
removed(child, events);
}
}
}
for (String child: children) {
add(path.join(ZNodeLabel.fromString(child)), zxid, events);
}
update(path, zxid, events, response);
break;
}
case GET_DATA:
{
ZNodePath path = (ZNodePath) ZNodeLabelVector.fromString(((Records.PathGetter) request).getPath());
update(path, zxid, events, response);
break;
}
case MULTI:
{
int xid = result.xid();
IMultiRequest requestRecord = (IMultiRequest) request;
IMultiResponse responseRecord = (IMultiResponse) response;
Iterator<MultiOpRequest> requests = requestRecord.iterator();
Iterator<MultiOpResponse> responses = responseRecord.iterator();
while (requests.hasNext()) {
handleResult(requests.next(),
ProtocolResponseMessage.of(xid, zxid, responses.next()),
events);
}
break;
}
case SET_DATA:
{
ZNodePath path = (ZNodePath) ZNodeLabelVector.fromString(((Records.PathGetter) request).getPath());
update(path, zxid, events, request, response);
break;
}
default:
break;
}
}
}
protected E add(ZNodePath path, long stamp, ImmutableSet.Builder<NodeWatchEvent> events) {
Iterator<ZNodeLabel> remaining = path.iterator();
E node = trie.root();
while (true) {
if (node.touch(stamp) < 0L) {
events.add(NodeWatchEvent.nodeCreated(node.path()));
if (! node.path().isRoot()) {
events.add(NodeWatchEvent.nodeChildrenChanged(node.parent().get().path()));
}
}
if (remaining.hasNext()) {
node = node.putIfAbsent(remaining.next());
} else {
break;
}
}
return node;
}
protected E remove(ZNodePath path, long stamp, ImmutableSet.Builder<NodeWatchEvent> events) {
return remove(trie.get(path), stamp, events);
}
protected E remove(E node, long stamp, ImmutableSet.Builder<NodeWatchEvent> events) {
if ((node != null) && (node.stamp() < stamp)) {
if (node.remove()) {
return removed(node, events);
}
}
return null;
}
protected E removed(E node, ImmutableSet.Builder<NodeWatchEvent> events) {
events.add(NodeWatchEvent.nodeDeleted(node.path()));
events.add(NodeWatchEvent.nodeChildrenChanged((node.parent().get().path())));
return node;
}
protected E update(ZNodePath path, long stamp, ImmutableSet.Builder<NodeWatchEvent> events, Record...records) {
E node = add(path, stamp, events);
events.addAll(node.update(stamp, records));
return node;
}
}