private cTSOTopicUpdateMessage SerializeUpdateField(object value, uint[] path) { try { var clsid = Serializer.GetClsid(value); if (!clsid.HasValue) { throw new Exception("Unable to serialize value with type: " + value.GetType()); } var update = new cTSOTopicUpdateMessage(); update.DotPath = path; update.Value = value; return(update); } catch (Exception ex) { LOG.Error(ex); throw ex; } }
public async void ApplyUpdate(cTSOTopicUpdateMessage update, ISecurityContext context) { try { var partialDotPath = new uint[update.DotPath.Length - 1]; Array.Copy(update.DotPath, partialDotPath, partialDotPath.Length); var path = await ResolveDotPath(partialDotPath); var target = path.GetValue(); if (target.Value == null) { throw new Exception("Cannot set property on null value"); } //Apply the change! var targetType = target.Value.GetType(); var finalPath = update.DotPath[update.DotPath.Length - 1]; var value = GetUpdateValue(update.Value); var provider = GetProvider(path.GetProvider()); var entity = path.GetEntity(); if (IsList(targetType)) { //Array, we expect the final path component to be an array index var arr = (IList)target.Value; var parent = path.GetParent(); var objectField = parent.Value.GetType().GetProperty(target.Name); if (finalPath < arr.Count) { //Update existing or remove on null (at index) if (IsNull(value)) { var removeItem = ((IList)target.Value)[(int)finalPath]; provider.DemandMutation(entity.Value, MutationType.ARRAY_REMOVE_ITEM, path.GetKeyPath(), removeItem, context); objectField.SetValue(parent.Value, GetGenericMethod(targetType, "RemoveAt").Invoke(target.Value, new object[] { (int)finalPath })); //TODO: make this async? if (target.Persist) { provider.PersistMutation(entity.Value, MutationType.ARRAY_REMOVE_ITEM, path.GetKeyPath(), removeItem); } } else { provider.DemandMutation(entity.Value, MutationType.ARRAY_SET_ITEM, path.GetKeyPath(), value, context); objectField.SetValue(parent.Value, GetGenericMethod(targetType, "SetItem").Invoke(target.Value, new object[] { (int)finalPath, value })); //arr[(int)finalPath] = value; //TODO: make this async? if (target.Persist) { provider.PersistMutation(entity.Value, MutationType.ARRAY_SET_ITEM, path.GetKeyPath(), value); } } } else if (finalPath >= arr.Count) { //Insert provider.DemandMutation(entity.Value, MutationType.ARRAY_SET_ITEM, path.GetKeyPath(), value, context); objectField.SetValue(parent.Value, GetGenericMethod(targetType, "Add").Invoke(target.Value, new object[] { value })); //arr.Add(value); if (target.Persist) { provider.PersistMutation(entity.Value, MutationType.ARRAY_SET_ITEM, path.GetKeyPath(), value); } } } else { //We expect a field value if (target.TypeId == 0) { throw new Exception("Trying to set field on unknown type"); } var _struct = DataDefinition.GetStruct(target.TypeId); var field = _struct.Fields.FirstOrDefault(x => x.ID == finalPath); if (field == null) { throw new Exception("Unknown field in dot path"); } var objectField = target.Value.GetType().GetProperty(field.Name); if (objectField == null) { throw new Exception("Unknown field in model: " + objectField.Name); } //If the value is null (0) and the field has a decoration of NullValueIndicatesDeletion //Delete the value instead of setting it var nullDelete = objectField.GetCustomAttribute <Key>(); if (nullDelete != null && IsNull(value)) { var parent = path.GetParent(); if (IsList(parent.Value)) { var listParent = path.Path[path.Path.Length - 3]; var lpField = listParent.Value.GetType().GetProperty(parent.Name); provider.DemandMutation(entity.Value, MutationType.ARRAY_REMOVE_ITEM, path.GetKeyPath(1), target.Value, context); lpField.SetValue(listParent.Value, GetGenericMethod(parent.Value.GetType(), "Remove", new Type[] { parent.Value.GetType().GenericTypeArguments[0] }) .Invoke(parent.Value, new object[] { target.Value })); //((IList)parent.Value).Remove(target.Value); if (parent.Persist) { provider.PersistMutation(entity.Value, MutationType.ARRAY_REMOVE_ITEM, path.GetKeyPath(1), target.Value); } } else { //TODO } } else { var persist = objectField.GetCustomAttribute <Persist>(); var clientSourced = (target.Value as AbstractModel)?.ClientSourced ?? false; //if the client is internally managing this value, do not update it. if (!clientSourced || objectField.GetCustomAttribute <ClientSourced>() == null) { provider.DemandMutation(entity.Value, MutationType.SET_FIELD_VALUE, path.GetKeyPath(objectField.Name), value, context); objectField.SetValue(target.Value, value); if (persist != null) { provider.PersistMutation(entity.Value, MutationType.SET_FIELD_VALUE, path.GetKeyPath(objectField.Name), value); } } } } } catch (Exception e) { if (e is SecurityException) { LOG.Error("Unauthorised data service update:" + e.Message); } else { LOG.Error(e, "Data service update failed."); } } }