package io.fathom.cloud.state; import io.fathom.cloud.Clock; import io.fathom.cloud.CloudException; import io.fathom.cloud.protobuf.ProtobufUtils; import io.fathom.cloud.protobuf.CloudCommons.ItemStateData; import java.util.Map; import com.google.common.base.Optional; import com.google.common.collect.Maps; 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.Message.Builder; public class ItemStates { static class ClassState { final Descriptor descriptor; Optional<FieldDescriptor> itemStateField; public ClassState(Descriptor descriptor) { this.descriptor = descriptor; } public synchronized Optional<FieldDescriptor> getItemStateField() { if (itemStateField == null) { FieldDescriptor found = null; for (FieldDescriptor field : descriptor.getFields()) { if (field.getType() != Type.MESSAGE) { continue; } if (field.getMessageType().equals(ItemStateData.getDescriptor())) { if (found != null) { throw new IllegalStateException(); } found = field; } } itemStateField = Optional.fromNullable(found); } return itemStateField; } } static final Map<Descriptor, ClassState> classes = Maps.newHashMap(); public static <T extends GeneratedMessage> boolean isDeleted(T msg) { ItemStateData itemState = getItemState(msg); if (itemState == null) { return false; } return itemState.hasDeletedAt(); } public static <T extends GeneratedMessage> ItemStateData getItemState(T msg) { FieldDescriptor field = findItemStateField(msg.getDescriptorForType()); if (field == null) { return null; } try { return (ItemStateData) msg.getField(field); } catch (Exception e) { throw new IllegalArgumentException("Error reading item state field", e); } } private static FieldDescriptor findItemStateField(Descriptor descriptorForType) { return getClassState(descriptorForType).getItemStateField().orNull(); } private static ClassState getClassState(Descriptor descriptor) { synchronized (classes) { ClassState classState = classes.get(descriptor); if (classState == null) { classState = new ClassState(descriptor); classes.put(descriptor, classState); } return classState; } } public static <T extends GeneratedMessage> T markDeleted(NumberedItemCollection<T> store, T msg) throws CloudException { Message.Builder builder = ProtobufUtils.newBuilder(msg.getClass()); builder.mergeFrom(msg); FieldDescriptor field = findItemStateField(msg.getDescriptorForType()); if (field == null) { throw new IllegalStateException(); } ItemStateData itemStateData; try { itemStateData = (ItemStateData) builder.getField(field); } catch (Exception e) { throw new IllegalArgumentException("Error reading item state field", e); } if (itemStateData.hasDeletedAt()) { throw new IllegalStateException(); } ItemStateData.Builder b = ItemStateData.newBuilder(itemStateData); b.setDeletedAt(Clock.getTimestamp()); builder.setField(field, b.build()); return store.update(builder); } public static boolean usesItemState(Descriptor descriptor) { return getClassState(descriptor).getItemStateField().isPresent(); } public static void setUpdatedAt(Builder item) { Descriptor descriptor = item.getDescriptorForType(); Optional<FieldDescriptor> itemStateField = getClassState(descriptor).getItemStateField(); if (itemStateField.isPresent()) { ItemStateData itemStateData; try { itemStateData = (ItemStateData) item.getField(itemStateField.get()); } catch (Exception e) { throw new IllegalArgumentException("Error reading item state field", e); } ItemStateData.Builder b = ItemStateData.newBuilder(itemStateData); b.setUpdatedAt(Clock.getTimestamp()); item.setField(itemStateField.get(), b.build()); } } public static void setCreatedAt(Builder item) { Descriptor descriptor = item.getDescriptorForType(); Optional<FieldDescriptor> itemStateField = getClassState(descriptor).getItemStateField(); if (itemStateField.isPresent()) { ItemStateData itemStateData; try { itemStateData = (ItemStateData) item.getField(itemStateField.get()); } catch (Exception e) { throw new IllegalArgumentException("Error reading item state field", e); } ItemStateData.Builder b = ItemStateData.newBuilder(itemStateData); b.setCreatedAt(Clock.getTimestamp()); item.setField(itemStateField.get(), b.build()); } } }