package edu.uw.zookeeper.data;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.concurrent.Callable;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ListenableFuture;
import edu.uw.zookeeper.client.ClientExecutor;
import edu.uw.zookeeper.common.Promise;
import edu.uw.zookeeper.data.Operations;
import edu.uw.zookeeper.data.Serializers;
import edu.uw.zookeeper.data.NameTrie;
import edu.uw.zookeeper.protocol.Operation;
import edu.uw.zookeeper.protocol.SessionListener;
import edu.uw.zookeeper.protocol.proto.ISetDataRequest;
import edu.uw.zookeeper.protocol.proto.Records;
public class Materializer<E extends Materializer.MaterializedNode<E,?>, O extends Operation.ProtocolResponse<?>> implements ClientExecutor<Records.Request,O, SessionListener> {
public static <E extends Materializer.MaterializedNode<E,?>, O extends Operation.ProtocolResponse<?>> Materializer<E,O> fromHierarchy(
Class<? extends E> rootType,
Serializers.ByteCodec<Object> codec,
ClientExecutor<? super Records.Request, O, SessionListener> client) {
SchemaElementLookup schema = SchemaElementLookup.fromHierarchy(rootType);
return fromSchema(schema, rootType, codec, client);
}
@SuppressWarnings("unchecked")
public static <E extends Materializer.MaterializedNode<E,?>, O extends Operation.ProtocolResponse<?>> Materializer<E,O> fromSchema(
SchemaElementLookup schema,
Class<? extends E> rootType,
Serializers.ByteCodec<Object> codec,
ClientExecutor<? super Records.Request, O, SessionListener> client) {
E root = null;
for (Constructor<?> ctor: rootType.getConstructors()) {
if (ctor.getParameterTypes().length == 2) {
try {
root = (E) ctor.newInstance(schema.get().root(), codec);
} catch (Exception e) {
throw new IllegalArgumentException(String.format("Error calling constructor %s", ctor), e);
}
break;
}
}
if (root == null) {
throw new IllegalArgumentException(String.format("Can't find constructor in %s", rootType));
}
return newInstance(schema, codec, client, root);
}
public static <E extends Materializer.MaterializedNode<E,?>, O extends Operation.ProtocolResponse<?>> Materializer<E,O> newInstance(
SchemaElementLookup schema,
Serializers.ByteCodec<Object> codec,
ClientExecutor<? super Records.Request,O,SessionListener> client,
E root) {
return new Materializer<E,O>(schema, codec, LockableZNodeCache.<E,Records.Request,O>newInstance(client, root));
}
public static <I extends Operation.Request, V extends Operation.ProtocolResponse<?>>
ListenableFuture<V> submit(ClientExecutor<? super I, V, ?> client, I request) {
return client.submit(request);
}
protected final LockableZNodeCache<E,Records.Request,O> cache;
protected final SchemaElementLookup schema;
protected final Serializers.ByteCodec<Object> codec;
protected Materializer(
SchemaElementLookup schema,
Serializers.ByteCodec<Object> codec,
LockableZNodeCache<E,Records.Request,O> cache) {
this.schema = schema;
this.codec = codec;
this.cache = cache;
}
public LockableZNodeCache<E,Records.Request,O> cache() {
return cache;
}
public SchemaElementLookup schema() {
return schema;
}
public Serializers.ByteCodec<Object> codec() {
return codec;
}
@Override
public void subscribe(SessionListener listener) {
cache.subscribe(listener);
}
@Override
public boolean unsubscribe(SessionListener listener) {
return cache.unsubscribe(listener);
}
@Override
public ListenableFuture<O> submit(Records.Request request) {
return cache.submit(request);
}
@Override
public ListenableFuture<O> submit(Records.Request request, Promise<O> promise) {
return cache.submit(request, promise);
}
public Operator<Operations.Requests.Create> create(ZNodePath path) {
return operator(createParameters(path));
}
public <V> Operator<? extends Operations.DataBuilder<? extends Records.Request, ?>> create(ZNodePath path, V data) {
if (data == null) {
return create(path);
} else {
return operator(Operations.Requests.serialized(createParameters(path), codec, data));
}
}
protected Operations.Requests.Create createParameters(ZNodePath path) {
ValueNode<ZNodeSchema> schema = ZNodeSchema.matchPath(this.schema.get(), path);
Operations.Requests.Create create = Operations.Requests.create()
.setMode(schema.get().getCreateMode())
.setAcl(ZNodeSchema.inheritedAcl(schema));
if (create.getMode().contains(CreateFlag.SEQUENTIAL)) {
if (path.toString().endsWith(Sequential.SUFFIX_PATTERN.toString())) {
path = ZNodePath.fromString(path.toString().substring(0, path.toString().length()-Sequential.SUFFIX_PATTERN.toString().length()));
}
}
return create.setPath(path);
}
public Operator<Operations.Requests.Delete> delete(ZNodePath path) {
return operator(Operations.Requests.delete().setPath(path));
}
public Operator<Operations.Requests.GetData> getData(ZNodePath path) {
return operator(Operations.Requests.getData().setPath(path));
}
public Operator<Operations.Requests.GetChildren> getChildren(ZNodePath path) {
return operator(Operations.Requests.getChildren().setPath(path));
}
public Operator<Operations.Requests.Exists> exists(ZNodePath path) {
return operator(Operations.Requests.exists().setPath(path));
}
public Operator<Operations.Requests.GetAcl> getAcl(ZNodePath path) {
return operator(Operations.Requests.getAcl().setPath(path));
}
public Operator<Operations.Requests.SetAcl> setAcl(ZNodePath path) {
return operator(Operations.Requests.setAcl().setPath(path));
}
public <V> Operator<Operations.Requests.SerializedData<ISetDataRequest, Operations.Requests.SetData, V>> setData(ZNodePath path, V data) {
Operations.Requests.SetData setData = Operations.Requests.setData().setPath(path);
return operator(Operations.Requests.serialized(setData, codec, data));
}
public Operator<Operations.Requests.Sync> sync(ZNodePath path) {
return operator(Operations.Requests.sync().setPath(path));
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).addValue(((Class<?>) schema().get().root().get().getDeclaration()).getSimpleName()).toString();
}
protected <T extends Operations.Builder<? extends Records.Request>> Operator<T> operator(T builder) {
return new Operator<T>(builder);
}
public class Operator<T extends Operations.Builder<? extends Records.Request>> implements Supplier<T>, Callable<ListenableFuture<O>> {
protected final T builder;
public Operator(T builder) {
this.builder = builder;
}
@Override
public ListenableFuture<O> call() {
return cache().submit(get().build());
}
@Override
public T get() {
return builder;
}
}
public abstract static class MaterializedNode<E extends MaterializedNode<E,?>,V> extends ZNodeCache.AbstractCacheNode<E,V> {
protected final ValueNode<ZNodeSchema> schema;
protected final Serializers.ByteCodec<Object> codec;
protected MaterializedNode(
ValueNode<ZNodeSchema> schema,
Serializers.ByteCodec<Object> codec,
NameTrie.Pointer<? extends E> parent) {
this(schema, codec, null, null, -1L, parent);
}
protected MaterializedNode(
ValueNode<ZNodeSchema> schema,
Serializers.ByteCodec<Object> codec,
V data,
Records.ZNodeStatGetter stat,
long stamp,
NameTrie.Pointer<? extends E> parent) {
this(schema, codec, data, stat, stamp, parent, Maps.<ZNodeName, E>newHashMap());
}
protected MaterializedNode(
ValueNode<ZNodeSchema> schema,
Serializers.ByteCodec<Object> codec,
V data,
Records.ZNodeStatGetter stat,
long stamp,
NameTrie.Pointer<? extends E> parent,
Map<ZNodeName, E> children) {
super(data, stat, stamp, parent, children);
this.schema = schema;
this.codec = codec;
}
public ValueNode<ZNodeSchema> schema() {
return schema;
}
@SuppressWarnings("unchecked")
@Override
protected V transformData(byte[] data) {
if ((data == null) || (data.length == 0)) {
return null;
}
V materialized;
Class<?> type = schema.get().getDataType();
if (type == Void.class) {
materialized = (V) data;
} else {
try {
materialized = (V) codec.fromBytes(data, type);
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
return materialized;
}
@Override
protected boolean equivalentData(V v1, V v2) {
return Objects.equal(v1, v2);
}
@SuppressWarnings("unchecked")
@Override
protected E newChild(ZNodeName name) {
NameTrie.Pointer<E> pointer = SimpleLabelTrie.weakPointer(name, (E) this);
ValueNode<ZNodeSchema> childSchema = (schema != null) ? ZNodeSchema.matchChild(schema, name) : null;
// find the right Constructor
Class<?> type;
if ((childSchema != null) && childSchema.get().getDeclaration() instanceof Class<?>) {
type = (Class<?>) childSchema.get().getDeclaration();
} else {
// TODO ?
type = getClass();
}
for (Constructor<?> ctor: type.getDeclaredConstructors()) {
if (ctor.getParameterTypes().length == 3) {
try {
return (E) ctor.newInstance(childSchema, codec, pointer);
} catch (Exception e) {
throw new UnsupportedOperationException(e);
}
}
}
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return MoreObjects.toStringHelper("")
.add("path", path())
.add("children", keySet())
.add("stamp", stamp())
.add("stat", stat())
.add("data", data())
.add("schema", schema()).toString();
}
}
}