package io.fathom.cloud.state; import io.fathom.cloud.CloudException; import io.fathom.cloud.state.StateStore.StateNode; import java.io.IOException; import java.util.List; import com.google.common.collect.Lists; import com.google.common.util.concurrent.SettableFuture; import com.google.protobuf.AbstractMessage.Builder; 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; public class NumberedItemCollection<T extends GeneratedMessage> extends ItemCollection { final Builder<?> template; final FieldDescriptor idField; final PrimaryKeyIndex primaryKeyIndex; final Descriptor descriptor; public NumberedItemCollection(StateNode parentNode, Builder<?> template, FieldDescriptor idField) { this(parentNode, null, template, idField, null); } public NumberedItemCollection(StateNode parentNode, Codec codec, Builder<?> template, FieldDescriptor idField, FieldDescriptor keyField) { super(parentNode, codec); this.template = template; this.idField = idField; if (keyField == null) { this.primaryKeyIndex = new RandomPrimaryKeyStrategy(parentNode, idField); } else if (keyField.getType() == Type.STRING) { this.primaryKeyIndex = new StringHashPrimaryKeyStrategy(parentNode, idField, keyField); } else { throw new IllegalArgumentException("Unknown key field type: " + keyField); } this.descriptor = template.getDescriptorForType(); } public List<T> list(StoreOptions... options) throws CloudException { List<T> items = deserializeChildren(parentNode, template.clone()); if (!usesItemState() || showDeleted(options)) { return items; } List<T> ret = Lists.newArrayList(); for (T item : items) { if (!ItemStates.isDeleted(item)) { ret.add(item); } } return ret; } public T find(long itemId, StoreOptions... options) throws CloudException { StateNode itemNode = parentNode.child(Long.toHexString(itemId)); T t = (T) deserialize(itemNode, template.clone()); if (t != null && usesItemState() && !showDeleted(options) && ItemStates.isDeleted(t)) { return null; } return t; } private static boolean showDeleted(StoreOptions[] options) { if (options == null || options.length == 0) { return false; } for (int i = 0; i < options.length; i++) { if (options[i] == StoreOptions.ShowDeleted) { return true; } } return false; } public T findByKey(String key) throws CloudException { StringHashPrimaryKeyStrategy pk = (StringHashPrimaryKeyStrategy) this.primaryKeyIndex; T t = pk.find(this, key); if (t != null && usesItemState() && ItemStates.isDeleted(t)) { return null; } return t; } public Watched<T> watch(long itemId) throws CloudException { StateNode itemNode = parentNode.child(Long.toHexString(itemId)); SettableFuture<Object> future = SettableFuture.create(); T t = (T) deserialize(itemNode, template.clone(), future); if (t != null && usesItemState() && ItemStates.isDeleted(t)) { return null; } return new Watched(t, future); } public T create(GeneratedMessage.Builder item) throws CloudException, DuplicateValueException { long id = ((Number) item.getField(idField)).longValue(); if (usesItemState()) { ItemStates.setCreatedAt(item); } if (id != 0) { throw new IllegalStateException(); } while (true) { id = primaryKeyIndex.createId(this, item); item.setField(idField, id); Message built = item.build(); ByteString data; try { data = codec.serialize(built); } catch (IOException e) { throw new CloudException("Error serializing data", e); } StateNode node = parentNode.child(Long.toHexString(id)); if (!node.create(data)) { continue; } return (T) built; } } // public T put(GeneratedMessage.Builder item) throws CloudException { // return (T) putItem(parentNode, item, idField); // } // // protected Message putItem(StateNode parent, GeneratedMessage.Builder // builder, FieldDescriptor idField) // throws CloudException { // long id = ((Number) builder.getField(idField)).longValue(); // boolean isCreate = false; // // if (id == 0) { // isCreate = true; // } // // while (true) { // if (isCreate) { // // Assign a new user id, randomly // id = getRandom(Integer.MAX_VALUE); // if (parent.hasChild(Long.toHexString(id))) { // continue; // } // builder.setField(idField, id); // } // // Message built = builder.build(); // // ByteString data; // try { // data = codec.serialize(built); // } catch (IOException e) { // throw new CloudException("Error serializing data", e); // } // // StateNode node = parent.child(Long.toHexString(id)); // // if (isCreate) { // if (!node.create(data)) { // continue; // } // } else { // node.update(data); // } // return built; // } // } public T delete(long itemId) throws CloudException { if (usesItemState()) { T found = find(itemId); if (found == null) { return null; } ItemStates.markDeleted(this, found); return found; } if (!this.primaryKeyIndex.allowDelete()) { throw new UnsupportedOperationException(); } StateNode itemNode = parentNode.child(Long.toHexString(itemId)); T v = (T) deserialize(itemNode, template.clone()); if (v == null) { return null; } if (!itemNode.delete()) { throw new IllegalStateException(); } return v; } private boolean usesItemState() { return ItemStates.usesItemState(descriptor); } public T update(Message.Builder item) throws CloudException { return (T) update(parentNode, item, idField); } protected Message update(StateNode parent, Message.Builder item, FieldDescriptor idField) throws CloudException { if (usesItemState()) { ItemStates.setUpdatedAt(item); } long id = ((Number) item.getField(idField)).longValue(); if (id == 0) { throw new IllegalArgumentException(); } StateNode node = parent.child(Long.toHexString(id)); return update(node, item); } 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) { return (CollectionBuilder<T>) super.keyField(keyFieldNumber); } @Override public NumberedItemCollection<T> create() { Codec codec = null; FieldDescriptor idField = getIdField(template); FieldDescriptor keyField = getKeyField(); return new NumberedItemCollection(parentNode, codec, template, idField, keyField); } } public static <T extends GeneratedMessage> CollectionBuilder<T> builder(StateNode parentNode, Class<T> protobufClass) { return new CollectionBuilder<T>(parentNode, protobufClass); } }