package io.fathom.cloud.state; import io.fathom.cloud.CloudException; import io.fathom.cloud.state.StateStore.StateNode; import io.fathom.cloud.zookeeper.ZookeeperClient; import java.util.List; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.util.concurrent.SettableFuture; import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor.Type; import com.google.protobuf.GeneratedMessage; import com.google.protobuf.Message; import com.google.protobuf.MessageOrBuilder; public class NamedItemCollection<T extends GeneratedMessage> extends ItemCollection { final GeneratedMessage.Builder template; final FieldDescriptor nameField; final Descriptor descriptor; public NamedItemCollection(StateNode parentNode, GeneratedMessage.Builder template, FieldDescriptor nameField) { super(parentNode); this.template = template; this.nameField = nameField; this.descriptor = template.getDescriptorForType(); } private boolean usesItemState() { return ItemStates.usesItemState(descriptor); } public List<T> list() throws CloudException { List<T> items = deserializeChildren(parentNode, template.clone()); if (!usesItemState()) { return items; } List<T> ret = Lists.newArrayList(); for (T item : items) { if (!ItemStates.isDeleted(item)) { ret.add(item); } } return ret; } public void delete(String name) throws StateStoreException { StateNode node = getNode(name); node.delete(); } public T find(String name) throws CloudException { StateNode node = getNode(name); T t = (T) deserialize(node, template.clone()); if (t != null && usesItemState() && ItemStates.isDeleted(t)) { return null; } return t; } public Watched<T> watch(String name) throws CloudException { StateNode node = getNode(name); SettableFuture<Object> future = SettableFuture.create(); T t = (T) deserialize(node, template.clone(), future); if (t != null && usesItemState() && ItemStates.isDeleted(t)) { return null; } return new Watched(t, future); } private StateNode getNode(String name) { if (Strings.isNullOrEmpty(name)) { throw new IllegalArgumentException(); } StateNode node = parentNode.child(ZookeeperClient.escape(name)); return node; } public T create(Message.Builder item) throws CloudException, DuplicateValueException { if (usesItemState()) { ItemStates.setCreatedAt(item); } String name = getKey(item); StateNode node = getNode(name); T built = (T) toMessage(item); ByteString data = built.toByteString(); if (!node.create(data)) { throw new DuplicateValueException(); } return built; } public T update(Message.Builder item) throws CloudException { if (usesItemState()) { ItemStates.setUpdatedAt(item); } String name = getKey(item); StateNode node = getNode(name); return (T) update(node, item); } private String getKey(MessageOrBuilder item) { String name = (String) item.getField(nameField); if (Strings.isNullOrEmpty(name)) { throw new IllegalArgumentException(); } return name; } public String getEtag() throws StateStoreException { Long v = parentNode.getChildrenChangeCount(); if (v == null) { return "-"; } return v.toString(); } public static class CollectionBuilder<T extends GeneratedMessage> extends CollectionBuilderBase<T> { public CollectionBuilder(StateNode parentNode, Class<T> protobufClass) { super(parentNode, protobufClass); } @Override public CollectionBuilder<T> idField(int idFieldNumber) { return (CollectionBuilder<T>) super.idField(idFieldNumber); } @Override public CollectionBuilder<T> keyField(int keyFieldNumber) { throw new UnsupportedOperationException(); } @Override public NamedItemCollection<T> create() { FieldDescriptor idField = getIdField(template); if (idField.getType() != Type.STRING) { throw new IllegalArgumentException(); } if (keyFieldNumber != null) { throw new IllegalArgumentException(); } return new NamedItemCollection(parentNode, template, idField); } } public static <T extends GeneratedMessage> CollectionBuilder<T> builder(StateNode parentNode, Class<T> protobufClass) { return new CollectionBuilder<T>(parentNode, protobufClass); } }