/// <summary> /// Changes the state and change order. /// </summary> /// <param name="descriptorData">The descriptor data.</param> /// <param name="state">The state.</param> /// <param name="changeOrder">The change order.</param> /// <remarks>There is no check that change order is unique as it's expensive. /// Use <see cref="GetNextChangeOrder"/> to treat the change (if any) as latest. /// </remarks> /// <exception cref="TaupoArgumentNullException"> /// When descriptorData is null. /// </exception> /// <exception cref="TaupoInvalidOperationException"> /// When state is Detached, change order is less then 0 or change order is 0 and state is not Unchanged. /// </exception> public void ChangeStateAndChangeOrder(DescriptorData descriptorData, EntityStates state, long changeOrder) { ExceptionUtilities.CheckArgumentNotNull(descriptorData, "descriptorData"); if (state == EntityStates.Detached) { throw new TaupoInvalidOperationException("Cannot create descriptor in a Detached state."); } if (changeOrder < 0) { throw new TaupoInvalidOperationException("Change order cannot be less then 0."); } if (changeOrder == 0 && state != EntityStates.Unchanged) { throw new TaupoInvalidOperationException("Change order cannot be 0 when state is not Unchanged."); } //// Note: there is no check that change order is unique as it's expensive. descriptorData.State = state; descriptorData.ChangeOrder = changeOrder; if (this.nextChangeOrder <= descriptorData.ChangeOrder && descriptorData.ChangeOrder != uint.MaxValue) { this.nextChangeOrder = descriptorData.ChangeOrder + 1; } }
/// <summary> /// Removes the descriptor data. /// </summary> /// <param name="descriptorData">The descriptor data.</param> /// <returns>False if the descriptor data is not found, true otherwise.</returns> public bool RemoveDescriptorData(DescriptorData descriptorData) { ExceptionUtilities.CheckArgumentNotNull(descriptorData, "descriptorData"); var entityData = descriptorData as EntityDescriptorData; if (entityData != null) { return this.RemoveEntityData(entityData); } else { return this.RemoveLinkData((LinkDescriptorData)descriptorData); } }
/// <summary> /// Calculates expected data for a request during DataServiceContext.SaveChanges for a particular descriptor. /// </summary> /// <param name="contextData">The context data</param> /// <param name="propertyValuesBeforeSave">The property values of the tracked client objects before the call to SaveChanges</param> /// <param name="descriptorData">The descriptor data</param> /// <param name="options">The save changes options</param> /// <returns>The expected client request</returns> public ExpectedClientRequest CalculateRequest(DataServiceContextData contextData, IDictionary<object, IEnumerable<NamedValue>> propertyValuesBeforeSave, DescriptorData descriptorData, SaveChangesOptions options) { ExceptionUtilities.CheckArgumentNotNull(contextData, "contextData"); ExceptionUtilities.CheckArgumentNotNull(descriptorData, "descriptorData"); ExceptionUtilities.CheckArgumentNotNull(propertyValuesBeforeSave, "propertyValuesBeforeSave"); var linkDescriptorData = descriptorData as LinkDescriptorData; var entityDescriptorData = descriptorData as EntityDescriptorData; var streamDescriptorData = descriptorData as StreamDescriptorData; ExpectedClientRequest request = null; if (linkDescriptorData != null) { if (linkDescriptorData.WillTriggerSeparateRequest()) { request = this.CreateLinkRequest(linkDescriptorData, options); } } else if (entityDescriptorData != null) { if (entityDescriptorData.State == EntityStates.Added) { request = this.CreateEntityInsertRequest(contextData, propertyValuesBeforeSave, entityDescriptorData, options); } else if (entityDescriptorData.State == EntityStates.Modified) { request = this.CreateEntityUpdateRequest(contextData, propertyValuesBeforeSave, entityDescriptorData, options); } else if (entityDescriptorData.State == EntityStates.Deleted) { request = this.CreateEntityDeleteRequest(entityDescriptorData, options); } } else if (streamDescriptorData != null) { if (streamDescriptorData.State == EntityStates.Added) { request = this.CreateStreamInsertRequest(contextData, streamDescriptorData, options); } else if (streamDescriptorData.State == EntityStates.Modified) { request = this.CreateStreamUpdateRequest(streamDescriptorData, options); } } if (request != null) { request.Headers[HttpHeaders.MaxDataServiceVersion] = ToClientHeaderFormat(contextData.MaxProtocolVersion); // perform sanity checks var missingHeaders = headersThatWillBeGenerated.Where(h => !request.Headers.ContainsKey(h)).ToArray(); ExceptionUtilities.Assert(missingHeaders.Length == 0, "Generated request was missing headers: {0}", string.Join(", ", missingHeaders)); ExceptionUtilities.CheckObjectNotNull(request.Uri, "Generated request was missing a Uri"); // sanity check: Client sends content-type header for delete request if (request.GetEffectiveVerb() == HttpVerb.Delete) { ExceptionUtilities.Assert( request.Headers[HttpHeaders.ContentType] == null, "Incorrect expectation: client should never send ContentType header for DELETE requests."); } } return request; }
/// <summary> /// Initializes a new instance of the <see cref="ChangeOperationResponseData"/> class. /// </summary> /// <param name="descriptorData">The descriptor data that represents tha change operation.</param> public ChangeOperationResponseData(DescriptorData descriptorData) { ExceptionUtilities.CheckArgumentNotNull(descriptorData, "descriptorData"); this.DescriptorData = descriptorData; }
private void UpdateResponse(DataServiceResponseData responseData, DescriptorData descriptor, HttpResponseData response) { var operationResponse = new ChangeOperationResponseData(descriptor); operationResponse.StatusCode = (int)response.StatusCode; foreach (var header in response.Headers) { operationResponse.Headers.Add(header.Key, header.Value); } responseData.Add(operationResponse); }
private void ApplyResponseToDescriptor(DescriptorData descriptor, HttpResponseData response) { var entityDescriptor = descriptor as EntityDescriptorData; var streamDescriptor = descriptor as StreamDescriptorData; if (entityDescriptor != null) { if (entityDescriptor.IsMediaLinkEntry && entityDescriptor.DefaultStreamState == EntityStates.Modified) { // in this case (and only this case), the response headers apply to the stream itself entityDescriptor.DefaultStreamDescriptor.UpdateFromHeaders(response.Headers); } else { entityDescriptor.UpdateFromHeaders(response.Headers); } if (response.StatusCode != HttpStatusCode.NoContent) { var entityInstance = response.DeserializeAndCast<EntityInstance>(this.parent.FormatSelector); entityDescriptor.UpdateFromPayload(entityInstance, this.contextData.BaseUri); } if (entityDescriptor.IsMediaLinkEntry && entityDescriptor.DefaultStreamState != EntityStates.Unchanged) { this.VerifyStreamClosed(entityDescriptor.DefaultStreamDescriptor); } } else { if (streamDescriptor != null) { streamDescriptor.UpdateFromHeaders(response.Headers); this.VerifyStreamClosed(streamDescriptor); } else { ExceptionUtilities.Assert(descriptor is LinkDescriptorData, "Descriptor is of unexpected type '{0}'", descriptor.GetType()); } } }
private void UpdateDescriptorState(DescriptorData descriptor) { var entityDescriptor = descriptor as EntityDescriptorData; var streamDescriptor = descriptor as StreamDescriptorData; if (entityDescriptor != null) { var initialState = entityDescriptor.State; if (entityDescriptor.State == EntityStates.Deleted) { this.contextData.RemoveDescriptorData(entityDescriptor); foreach (var link in this.contextData.LinkDescriptorsData.Where(l => l.SourceDescriptor == entityDescriptor || l.TargetDescriptor == entityDescriptor).ToList()) { this.contextData.RemoveDescriptorData(link); } } else { if (entityDescriptor.State == EntityStates.Added) { this.contextData.LinkDescriptorsData .Where(l => l.SourceDescriptor == entityDescriptor && l.State != EntityStates.Deleted && l.TargetDescriptor.State != EntityStates.Added) .ForEach(l => l.State = EntityStates.Unchanged); if (entityDescriptor.ParentForInsert != null) { var parentLink = this.contextData.LinkDescriptorsData .SingleOrDefault(l => l.SourceDescriptor.Entity == entityDescriptor.ParentForInsert && l.SourcePropertyName == entityDescriptor.ParentPropertyForInsert && l.TargetDescriptor == descriptor); ExceptionUtilities.CheckObjectNotNull(parentLink, "Could not find parent link descriptor for entity descriptor: '{0}'", entityDescriptor); ExceptionUtilities.Assert(parentLink.State == EntityStates.Added, "Parent link for entity descriptor '{0}' was not in the added state", entityDescriptor); parentLink.State = EntityStates.Unchanged; } entityDescriptor.InsertLink = null; entityDescriptor.ParentForInsert = null; entityDescriptor.ParentPropertyForInsert = null; } entityDescriptor.State = EntityStates.Unchanged; } if (entityDescriptor.IsMediaLinkEntry) { if (entityDescriptor.DefaultStreamState == EntityStates.Added) { // the stream insert should always leave the entity in modified state, so that the properties are updated entityDescriptor.State = EntityStates.Modified; } else if (entityDescriptor.DefaultStreamState == EntityStates.Modified) { // if the stream was being updated, the entity's state should not have changed entityDescriptor.State = initialState; } entityDescriptor.DefaultStreamState = EntityStates.Unchanged; } } else { if (streamDescriptor == null) { ExceptionUtilities.CheckObjectNotNull(descriptor as LinkDescriptorData, "Descriptor is of unexpected type '{0}'", descriptor.GetType()); } if (descriptor.State == EntityStates.Deleted) { this.contextData.RemoveDescriptorData(descriptor); } else { descriptor.State = EntityStates.Unchanged; } } }
private void ApplyResponseAndUpdateState(DescriptorData descriptor, KeyValuePair<IHttpRequest, HttpResponseData> pair) { this.ApplyResponseToDescriptor(descriptor, pair.Value); if (!pair.Value.StatusCode.IsError()) { this.UpdateDescriptorState(descriptor); } }
private static void CheckStateIsNot(EntityStates state, DescriptorData descriptorData, string errorMessage, string argumentName) { if (descriptorData.State == state) { throw new TaupoInvalidOperationException( string.Format(CultureInfo.InvariantCulture, "{0} {1} is in {2} state.", errorMessage, argumentName, state)); } }
/// <summary> /// Initializes a new instance of the <see cref="ChangeData"/> class. /// </summary> /// <param name="descriptorData">The descriptor data.</param> protected ChangeData(DescriptorData descriptorData) { this.State = descriptorData.State; this.DescriptorData = descriptorData; }
private ChangeData CreateChangeData(DescriptorData descriptorData, IDictionary<object, IEnumerable<NamedValue>> propertyValuesBeforeSave) { var entityDescriptorData = descriptorData as EntityDescriptorData; if (entityDescriptorData != null) { IEnumerable<NamedValue> propertyValues; ExceptionUtilities.Assert(propertyValuesBeforeSave.TryGetValue(entityDescriptorData.Entity, out propertyValues), "Could not find property values for descriptor: {0}", entityDescriptorData); return EntityChangeData.Create(entityDescriptorData, propertyValues); } var linkDescriptorData = descriptorData as LinkDescriptorData; ExceptionUtilities.CheckObjectNotNull(linkDescriptorData, "Descriptor was neither an entity nor a link"); return LinkChangeData.Create(linkDescriptorData); }
/// <summary> /// Returns a value indicating whether the descriptor should be updated based on its state and the context's merge option /// </summary> /// <param name="contextData">The context data</param> /// <param name="descriptorData">The descriptor data</param> /// <param name="isNewDescriptor">A value indicating whether the descriptor has just been created</param> /// <returns>True if changes should be applied, false otherwise</returns> public static bool ShouldApplyChangeToDescriptor(this DataServiceContextData contextData, DescriptorData descriptorData, bool isNewDescriptor) { return isNewDescriptor || contextData.MergeOption == MergeOption.OverwriteChanges || (contextData.MergeOption == MergeOption.PreserveChanges && descriptorData.State == EntityStates.Unchanged); }
private static void SetDetachedState(DescriptorData data) { data.State = EntityStates.Detached; }
private void VerifyDescriptor(DescriptorData expected, Descriptor actual, int responseOrder) { EntityDescriptorData entityDescriptorData = expected as EntityDescriptorData; LinkDescriptorData linkDescriptorData = expected as LinkDescriptorData; StreamDescriptorData streamDescriptorData = expected as StreamDescriptorData; if (entityDescriptorData != null) { EntityDescriptor entityDescriptor = actual as EntityDescriptor; this.Assert.IsNotNull(entityDescriptor, GetVerificationFailureMessage(responseOrder, "Unexpected descriptor type:\r\nExpected: {0}\r\nActual: {1}\r\nExpected descriptor data: {2}.", typeof(EntityDescriptor).Name, actual.GetType().Name, entityDescriptorData)); this.Assert.AreSame( entityDescriptorData.Entity, entityDescriptor.Entity, GetVerificationFailureMessage(responseOrder, "Entity verification failed for the entity descriptor data: {0}.", expected)); } else if (linkDescriptorData != null) { LinkDescriptor linkDescriptor = actual as LinkDescriptor; this.Assert.IsNotNull(linkDescriptor, GetVerificationFailureMessage(responseOrder, "Unexpected descriptor type:\r\nExpected: {0}\r\nActual: {1}\r\nExpected descriptor data: {2}.", typeof(LinkDescriptor).Name, actual.GetType().Name, linkDescriptorData)); bool notMatch = linkDescriptorData.SourceDescriptor.Entity != linkDescriptor.Source || (linkDescriptorData.TargetDescriptor == null && linkDescriptor.Target != null) || (linkDescriptorData.TargetDescriptor != null && linkDescriptorData.TargetDescriptor.Entity != linkDescriptor.Target) || linkDescriptorData.SourcePropertyName != linkDescriptor.SourceProperty; this.Assert.IsFalse(notMatch, GetVerificationFailureMessage(responseOrder, "Link verification failed.\r\nExpected: {0}\r\nActual: {1}", linkDescriptorData, linkDescriptor.ToTraceString())); } else { #if WINDOWS_PHONE throw new TaupoNotSupportedException("StreamDescriptors are not supported on Windows Phone"); #else ExceptionUtilities.CheckObjectNotNull(streamDescriptorData, "Expected was not an entity, link, or stream descriptor: {0}", expected); StreamDescriptor streamDescriptor = actual as StreamDescriptor; this.Assert.IsNotNull(streamDescriptor, GetVerificationFailureMessage(responseOrder, "Unexpected descriptor type:\r\nExpected: {0}\r\nActual: {1}\r\nExpected descriptor data: {2}.", typeof(StreamDescriptor).Name, actual.GetType().Name, streamDescriptorData)); this.Assert.AreEqual(streamDescriptorData.State.ToProductEnum(), streamDescriptor.State, GetVerificationFailureMessage(responseOrder, "Stream descriptor state verification failed.")); this.Assert.AreEqual(streamDescriptorData.Name, streamDescriptor.StreamLink.Name, GetVerificationFailureMessage(responseOrder, "Stream descriptor name verification failed.")); this.Assert.AreEqual(streamDescriptorData.ETag, streamDescriptor.StreamLink.ETag, GetVerificationFailureMessage(responseOrder, "Stream descriptor etag verification failed.")); this.Assert.AreEqual(streamDescriptorData.ContentType, streamDescriptor.StreamLink.ContentType, GetVerificationFailureMessage(responseOrder, "Stream descriptor content type verification failed.")); this.Assert.AreEqual(streamDescriptorData.EditLink, streamDescriptor.StreamLink.EditLink, GetVerificationFailureMessage(responseOrder, "Stream descriptor edit link verification failed.")); this.Assert.AreEqual(streamDescriptorData.SelfLink, streamDescriptor.StreamLink.SelfLink, GetVerificationFailureMessage(responseOrder, "Stream descriptor self link verification failed.")); #endif } }
private static void SetContentIDHeader(DescriptorData descriptor, ExpectedClientRequest expectedRequest, bool isBatch) { if (isBatch) { expectedRequest.Headers[HttpHeaders.ContentId] = descriptor.ChangeOrder.ToString(CultureInfo.InvariantCulture); var entityDescriptorData = descriptor as EntityDescriptorData; if (entityDescriptorData != null && entityDescriptorData.Identity == null) { entityDescriptorData.Identity = new Uri("$" + descriptor.ChangeOrder, UriKind.Relative); entityDescriptorData.EditLink = entityDescriptorData.Identity; } } else { expectedRequest.Headers[HttpHeaders.ContentId] = null; } }
/// <summary> /// Returns a value indicating whether the descriptor should be updated based on its state and the context's merge option /// </summary> /// <param name="contextData">The context data</param> /// <param name="descriptorData">The descriptor data</param> /// <param name="isNewDescriptor">A value indicating whether the descriptor has just been created</param> /// <returns>True if changes should be applied, false otherwise</returns> public static bool ShouldApplyChangeToDescriptor(this DataServiceContextData contextData, DescriptorData descriptorData, bool isNewDescriptor) { return(isNewDescriptor || contextData.MergeOption == MergeOption.OverwriteChanges || (contextData.MergeOption == MergeOption.PreserveChanges && descriptorData.State == EntityStates.Unchanged)); }