diff --git a/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataContext.cs b/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataContext.cs index a009a610f8..4aab88050e 100644 --- a/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataContext.cs +++ b/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataContext.cs @@ -166,41 +166,46 @@ internal static ODataResourceMetadataContext Create( /// The resource instance. /// The serialization info of the resource for writing without model. /// The edm entity type of the resource + /// Whether key properties are required to be returned /// Key value pair array internal static KeyValuePair[] GetKeyProperties( ODataResourceBase resource, ODataResourceSerializationInfo serializationInfo, - IEdmEntityType actualEntityType) + IEdmEntityType actualEntityType, + bool requiresId) { + Debug.Assert(resource != null, "GetKeyProperties called for a null resource."); + KeyValuePair[] keyProperties = null; - string actualEntityTypeName = null; + string actualEntityTypeName = string.IsNullOrEmpty(resource.TypeName) ? actualEntityType?.FullName() : resource.TypeName; + // if we have serializationInfo, try that first if (serializationInfo != null) { - if (String.IsNullOrEmpty(resource.TypeName)) - { - throw new ODataException(Strings.ODataResourceTypeContext_ODataResourceTypeNameMissing); - } - - actualEntityTypeName = resource.TypeName; keyProperties = ODataResourceMetadataContextWithoutModel.GetPropertiesBySerializationInfoPropertyKind(resource, ODataPropertyKind.Key, actualEntityTypeName); } - else + + // if we didn't get any keys from serializationInfo, try using entity type + if ((keyProperties == null || keyProperties.Length == 0) && actualEntityType != null) { - keyProperties = GetPropertyValues(actualEntityType.Key(), resource, actualEntityType, /*isKeyProperty*/ true, /*isRequired*/ true).ToArray(); + keyProperties = GetPropertyValues(actualEntityType.Key(), resource, actualEntityType, requiresId).ToArray(); + } + + if (!ValidateEntityTypeHasKeyProperties(keyProperties, actualEntityTypeName, requiresId)) + { + return Enumerable.Empty>().ToArray(); } - ValidateEntityTypeHasKeyProperties(keyProperties, actualEntityTypeName); return keyProperties; } - private static IEnumerable> GetPropertyValues(IEnumerable properties, ODataResourceBase resource, IEdmEntityType actualEntityType, bool isKeyProperty, bool isRequired) + private static IEnumerable> GetPropertyValues(IEnumerable properties, ODataResourceBase resource, IEdmEntityType actualEntityType, bool isRequired) { string actualEntityTypeName = actualEntityType.FullName(); object primitiveValue; foreach (IEdmStructuralProperty property in properties) { - if (TryGetPrimitiveOrEnumPropertyValue(resource, property.Name, actualEntityTypeName, isKeyProperty, isRequired, out primitiveValue)) + if (TryGetPrimitiveOrEnumPropertyValue(resource, property.Name, actualEntityTypeName, isRequired, out primitiveValue)) { yield return new KeyValuePair(property.Name, primitiveValue); } @@ -213,11 +218,10 @@ private static IEnumerable> GetPropertyValues(IEnum /// The resource to get the property value. /// Name of the property. /// The name of the entity type to get the property value. - /// true if the property is a key property, false otherwise. + /// true, if the property value is required to be non-null. /// returned value, or null if no value is found. - /// true, if the property value is required. /// true, if the primitive value is found, otherwise false. - private static bool TryGetPrimitiveOrEnumPropertyValue(ODataResourceBase resource, string propertyName, string entityTypeName, bool isKeyProperty, bool isRequired, out object value) + private static bool TryGetPrimitiveOrEnumPropertyValue(ODataResourceBase resource, string propertyName, string entityTypeName, bool isRequired, out object value) { Debug.Assert(resource != null, "resource != null"); @@ -235,7 +239,7 @@ private static bool TryGetPrimitiveOrEnumPropertyValue(ODataResourceBase resourc } } - value = GetPrimitiveOrEnumPropertyValue(entityTypeName, property, isKeyProperty); + value = GetPrimitiveOrEnumPropertyValue(entityTypeName, property, isRequired); return true; } @@ -244,12 +248,12 @@ private static bool TryGetPrimitiveOrEnumPropertyValue(ODataResourceBase resourc /// /// The name of the entity type to get the property value. /// The ODataProperty to get the value from. - /// true if the property is a key property, false otherwise. + /// true if property must not be null, false otherwise. /// The value of the property. - private static object GetPrimitiveOrEnumPropertyValue(string entityTypeName, ODataProperty property, bool isKeyProperty) + private static object GetPrimitiveOrEnumPropertyValue(string entityTypeName, ODataProperty property, bool validateNotNull) { object propertyValue = property.Value; - if (propertyValue == null && isKeyProperty) + if (propertyValue == null && validateNotNull) { throw new ODataException(Strings.ODataResourceMetadataContext_NullKeyValue(property.Name, entityTypeName)); } @@ -267,13 +271,39 @@ private static object GetPrimitiveOrEnumPropertyValue(string entityTypeName, ODa /// /// Key properties of the resource. /// The entity type name of the resource. - private static void ValidateEntityTypeHasKeyProperties(KeyValuePair[] keyProperties, string actualEntityTypeName) + /// Whether to throw if validation fails. + /// True, if validation succeeds, or false if validation fails. + private static bool ValidateEntityTypeHasKeyProperties(KeyValuePair[] keyProperties, string actualEntityTypeName, bool throwOnError) { - Debug.Assert(keyProperties != null, "keyProperties != null"); if (keyProperties == null || keyProperties.Length == 0) { - throw new ODataException(Strings.ODataResourceMetadataContext_EntityTypeWithNoKeyProperties(actualEntityTypeName)); + if (throwOnError) + { + throw new ODataException(Strings.ODataResourceMetadataContext_EntityTypeWithNoKeyProperties(actualEntityTypeName)); + } + else + { + return false; + } + } + + for (int keyProperty = 0; keyProperty < keyProperties.Length; keyProperty++) + { + object keyValue = keyProperties[keyProperty].Value; + if (keyValue == null || (keyValue is ODataValue && !(keyValue is ODataEnumValue))) + { + if (throwOnError) + { + throw new ODataException(Strings.ODataResourceMetadataContext_NullKeyValue(keyProperties[keyProperty].Key, actualEntityTypeName)); + } + else + { + return false; + } + } } + + return true; } /// @@ -288,14 +318,14 @@ private static KeyValuePair[] GetPropertiesBySerializationInfoPr Debug.Assert(resource != null, "resource != null"); Debug.Assert(propertyKind == ODataPropertyKind.Key || propertyKind == ODataPropertyKind.ETag, "propertyKind == ODataPropertyKind.Key || propertyKind == ODataPropertyKind.ETag"); - List> properties = new List>(); ; + List> properties = new List>(); if (resource.NonComputedProperties != null) { foreach(ODataProperty property in resource.NonComputedProperties) { if(property.SerializationInfo != null && property.SerializationInfo.PropertyKind == propertyKind) { - properties.Add(new KeyValuePair(property.Name, GetPrimitiveOrEnumPropertyValue(actualEntityTypeName, property, propertyKind == ODataPropertyKind.Key))); + properties.Add(new KeyValuePair(property.Name, GetPrimitiveOrEnumPropertyValue(actualEntityTypeName, property, false))); } } } @@ -357,11 +387,8 @@ public override ICollection> KeyProperties { if (this.keyProperties == null) { - this.keyProperties = GetPropertiesBySerializationInfoPropertyKind(this.resource, ODataPropertyKind.Key, this.ActualResourceTypeName); - if (this.requiresId) - { - ValidateEntityTypeHasKeyProperties(this.keyProperties, this.ActualResourceTypeName); - } + IEdmEntityType entityType = this.ActualResourceType as IEdmEntityType; + this.keyProperties = GetKeyProperties(this.resource, this.serializationInfo, entityType, this.requiresId); } return this.keyProperties; @@ -483,16 +510,12 @@ public override ICollection> KeyProperties IEdmEntityType entityType = this.actualResourceType as IEdmEntityType; if (entityType != null) { - this.keyProperties = keyProperties = GetPropertyValues(entityType.Key(), resource, entityType, /*isKeyProperty*/ true, this.requiresId).ToArray(); - - if (this.requiresId) - { - ValidateEntityTypeHasKeyProperties(this.keyProperties, this.ActualResourceTypeName); - } + this.keyProperties = GetPropertyValues(entityType.Key(), resource, entityType, this.requiresId).ToArray(); } - else + + if (!ValidateEntityTypeHasKeyProperties(this.keyProperties, this.ActualResourceTypeName, this.requiresId)) { - this.keyProperties = Enumerable.Empty>().ToArray(); + return Enumerable.Empty>().ToArray(); } } @@ -512,7 +535,7 @@ public override IEnumerable> ETagProperties IEdmEntityType actualEntityType = this.actualResourceType as IEdmEntityType; IEnumerable properties = this.ComputeETagPropertiesFromAnnotation(); this.etagProperties = properties.Any() - ? GetPropertyValues(properties, resource, actualEntityType, /*isKeyProperty*/false, /*isRequired*/ true) + ? GetPropertyValues(properties, resource, actualEntityType, /*isRequired*/ false) : EmptyProperties; } diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs index a6235ace62..156a7b70d6 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs @@ -2426,20 +2426,18 @@ private void StartNestedStreamInfo(ODataJsonLightReaderStreamInfo readerStreamIn /// True if successfully append key segment. private bool TryAppendEntitySetKeySegment(ref ODataPath odataPath) { - try + if (EdmExtensionMethods.HasKey(this.CurrentScope.NavigationSource, this.CurrentScope.ResourceType as IEdmStructuredType)) { - if (EdmExtensionMethods.HasKey(this.CurrentScope.NavigationSource, this.CurrentScope.ResourceType as IEdmStructuredType)) + IEdmEntityType currentEntityType = this.CurrentScope.ResourceType as IEdmEntityType; + ODataResourceBase resource = this.CurrentScope.Item as ODataResourceBase; + KeyValuePair[] keys = ODataResourceMetadataContext.GetKeyProperties(resource, null, currentEntityType, false); + if (keys.Length == 0) { - IEdmEntityType currentEntityType = this.CurrentScope.ResourceType as IEdmEntityType; - ODataResourceBase resource = this.CurrentScope.Item as ODataResourceBase; - KeyValuePair[] keys = ODataResourceMetadataContext.GetKeyProperties(resource, null, currentEntityType); - odataPath = odataPath.AddKeySegment(keys, currentEntityType, this.CurrentScope.NavigationSource); + odataPath = null; + return false; } - } - catch (ODataException) - { - odataPath = null; - return false; + + odataPath = odataPath.AddKeySegment(keys, currentEntityType, this.CurrentScope.NavigationSource); } return true; diff --git a/src/Microsoft.OData.Core/ODataWriterCore.cs b/src/Microsoft.OData.Core/ODataWriterCore.cs index 676af6a8c7..e690217dc0 100644 --- a/src/Microsoft.OData.Core/ODataWriterCore.cs +++ b/src/Microsoft.OData.Core/ODataWriterCore.cs @@ -1,4453 +1,4392 @@ -//--------------------------------------------------------------------- -// -// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. -// -//--------------------------------------------------------------------- - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.OData.Edm; -using Microsoft.OData.Evaluation; -using Microsoft.OData.Metadata; -using Microsoft.OData.UriParser; - -namespace Microsoft.OData -{ - /// - /// Base class for OData writers that verifies a proper sequence of write calls on the writer. - /// - internal abstract class ODataWriterCore : ODataWriter, IODataOutputInStreamErrorListener, IODataStreamListener - { - /// The writer validator to use. - protected readonly IWriterValidator WriterValidator; - - /// The output context to write to. - private readonly ODataOutputContext outputContext; - - /// True if the writer was created for writing a resourceSet; false when it was created for writing a resource. - private readonly bool writingResourceSet; - - /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer. - private readonly IODataReaderWriterListener listener; - - /// Stack of writer scopes to keep track of the current context of the writer. - private readonly ScopeStack scopeStack = new ScopeStack(); - - /// The number of entries which have been started but not yet ended. - private int currentResourceDepth; - - /// - /// Constructor. - /// - /// The output context to write to. - /// The navigation source we are going to write resource set for. - /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). - /// True if the writer is created for writing a resourceSet; false when it is created for writing a resource. - /// True if the writer is created for writing a delta response; false otherwise. This parameter is ignored and will be removed in the next major release. - /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer. - protected ODataWriterCore( - ODataOutputContext outputContext, - IEdmNavigationSource navigationSource, - IEdmStructuredType resourceType, - bool writingResourceSet, - bool writingDelta = false, - IODataReaderWriterListener listener = null) - { - Debug.Assert(outputContext != null, "outputContext != null"); - - this.outputContext = outputContext; - this.writingResourceSet = writingResourceSet; - this.WriterValidator = outputContext.WriterValidator; - this.Version = outputContext.MessageWriterSettings.Version; - - if (navigationSource != null && resourceType == null) - { - resourceType = this.outputContext.EdmTypeResolver.GetElementType(navigationSource); - } - - ODataUri odataUri = outputContext.MessageWriterSettings.ODataUri.Clone(); - - // Remove key for top level resource - if (!writingResourceSet && odataUri != null && odataUri.Path != null) - { - odataUri.Path = odataUri.Path.TrimEndingKeySegment(); - } - - this.listener = listener; - - this.scopeStack.Push(new Scope(WriterState.Start, /*item*/null, navigationSource, resourceType, /*skipWriting*/false, outputContext.MessageWriterSettings.SelectedProperties, odataUri, /*enableDelta*/ true)); - this.CurrentScope.DerivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationSource)?.ToList(); - } - - /// - /// An enumeration representing the current state of the writer. - /// - internal enum WriterState - { - /// The writer is at the start; nothing has been written yet. - Start, - - /// The writer is currently writing a resource. - Resource, - - /// The writer is currently writing a resourceSet. - ResourceSet, - - /// The writer is currently writing a delta resource set. - DeltaResourceSet, - - /// The writer is currently writing a deleted resource. - DeletedResource, - - /// The writer is currently writing a delta link. - DeltaLink, - - /// The writer is currently writing a delta deleted link. - DeltaDeletedLink, - - /// The writer is currently writing a nested resource info (possibly an expanded link but we don't know yet). - /// - /// This state is used when a nested resource info was started but we didn't see any children for it yet. - /// - NestedResourceInfo, - - /// The writer is currently writing a nested resource info with content. - /// - /// This state is used when a nested resource info with either an entity reference link or expanded resourceSet/resource was written. - /// - NestedResourceInfoWithContent, - - /// The writer is currently writing a primitive value. - Primitive, - - /// The writer is currently writing a single property. - Property, - - /// The writer is currently writing a stream value. - Stream, - - /// The writer is currently writing a string value. - String, - - /// The writer has completed; nothing can be written anymore. - Completed, - - /// The writer is in error state; nothing can be written anymore. - Error - } - - /// - /// OData Version being written. - /// - internal ODataVersion? Version { get; } - - /// - /// The current scope for the writer. - /// - protected Scope CurrentScope - { - get - { - Debug.Assert(this.scopeStack.Count > 0, "We should have at least one active scope all the time."); - return this.scopeStack.Peek(); - } - } - - /// - /// The current state of the writer. - /// - protected WriterState State - { - get - { - return this.CurrentScope.State; - } - } - - /// - /// true if the writer should not write any input specified and should just skip it. - /// - protected bool SkipWriting - { - get - { - return this.CurrentScope.SkipWriting; - } - } - - /// - /// A flag indicating whether the writer is at the top level. - /// - protected bool IsTopLevel - { - get - { - Debug.Assert(this.State != WriterState.Start && this.State != WriterState.Completed, "IsTopLevel should only be called while writing the payload."); - - // there is the root scope at the top (when the writer has not started or has completed) - // and then the top-level scope (the top-level resource/resourceSet item) as the second scope on the stack - return this.scopeStack.Count == 2; - } - } - - /// - /// The scope level the writer is writing. - /// - protected int ScopeLevel - { - get { return this.scopeStack.Count; } - } - - /// - /// Returns the immediate parent link which is being expanded, or null if no such link exists - /// - protected ODataNestedResourceInfo ParentNestedResourceInfo - { - get - { - Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.ResourceSet || this.State == WriterState.DeltaResourceSet, "ParentNestedResourceInfo should only be called while writing a resource or a resourceSet."); - - Scope linkScope = this.scopeStack.ParentOrNull; - return linkScope == null ? null : (linkScope.Item as ODataNestedResourceInfo); - } - } - - /// - /// Returns the nested info that current resource belongs to. - /// - protected ODataNestedResourceInfo BelongingNestedResourceInfo - { - get - { - Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.ResourceSet || this.State == WriterState.DeletedResource || this.State == WriterState.DeltaResourceSet, "BelongingNestedResourceInfo should only be called while writing a (deleted) resource or a (delta) resourceSet."); - - Scope linkScope = this.scopeStack.ParentOrNull; - - // For single navigation - if (linkScope is NestedResourceInfoScope) - { - return linkScope.Item as ODataNestedResourceInfo; - } - else if (linkScope is ResourceSetBaseScope) - { - // For resource under collection of navigation/complex, parent is ResourceSetScope, so we need find parent of parent. - linkScope = this.scopeStack.ParentOfParent; - return linkScope == null ? null : (linkScope.Item as ODataNestedResourceInfo); - } - else - { - return null; - } - } - } - - /// - /// Returns the resource type of the immediate parent resource for which a nested resource info is being written. - /// - protected IEdmStructuredType ParentResourceType - { - get - { - Debug.Assert( - this.State == WriterState.NestedResourceInfo || this.State == WriterState.NestedResourceInfoWithContent, - "ParentResourceType should only be called while writing a nested resource info (with or without content), or within an untyped ResourceSet."); - Scope resourceScope = this.scopeStack.Parent; - return resourceScope.ResourceType; - } - } - - /// - /// Returns the navigation source of the immediate parent resource for which a nested resource info is being written. - /// - protected IEdmNavigationSource ParentResourceNavigationSource - { - get - { - Scope resourceScope = this.scopeStack.Parent; - return resourceScope == null ? null : resourceScope.NavigationSource; - } - } - - /// - /// Returns the parent scope of current scope. - /// - protected Scope ParentScope - { - get - { - Debug.Assert(this.scopeStack.Count > 1); - return this.scopeStack.Parent; - } - } - - /// - /// Returns the number of items seen so far on the current resource set scope. - /// - /// Can only be accessed on a resource set scope. - protected int ResourceSetScopeResourceCount - { - get - { - Debug.Assert(this.State == WriterState.ResourceSet, "ResourceSetScopeResourceCount should only be called while writing a resource set."); - return ((ResourceSetBaseScope)this.CurrentScope).ResourceCount; - } - } - - /// - /// Checker to detect duplicate property names. - /// - protected IDuplicatePropertyNameChecker DuplicatePropertyNameChecker - { - get - { - Debug.Assert( - this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.NestedResourceInfo || this.State == WriterState.NestedResourceInfoWithContent || this.State == WriterState.Property, - "PropertyAndAnnotationCollector should only be called while writing a resource or an (expanded or deferred) nested resource info."); - - ResourceBaseScope resourceScope; - switch (this.State) - { - case WriterState.DeletedResource: - case WriterState.Resource: - resourceScope = (ResourceBaseScope)this.CurrentScope; - break; - case WriterState.Property: - case WriterState.NestedResourceInfo: - case WriterState.NestedResourceInfoWithContent: - resourceScope = (ResourceBaseScope)this.scopeStack.Parent; - break; - default: - throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_PropertyAndAnnotationCollector)); - } - - return resourceScope.DuplicatePropertyNameChecker; - } - } - - /// - /// The structured type of the current resource. - /// - protected IEdmStructuredType ResourceType - { - get - { - return this.CurrentScope.ResourceType; - } - } - - /// - /// Returns the parent nested resource info scope of a resource in an expanded link (if it exists). - /// The resource can either be the content of the expanded link directly or nested inside a resourceSet. - /// - /// The parent navigation scope of a resource in an expanded link (if it exists). - protected NestedResourceInfoScope ParentNestedResourceInfoScope - { - get - { - Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.ResourceSet || this.State == WriterState.DeltaResourceSet, "ParentNestedResourceInfoScope should only be called while writing a resource or a resourceSet."); - Debug.Assert(this.scopeStack.Count >= 2, "We should have at least the resource scope and the start scope on the stack."); - - Scope parentScope = this.scopeStack.Parent; - if (parentScope.State == WriterState.Start) - { - // Top-level resource. - return null; - } - - if (parentScope.State == WriterState.ResourceSet || parentScope.State == WriterState.DeltaResourceSet) - { - Debug.Assert(this.scopeStack.Count >= 3, "We should have at least the resource scope, the resourceSet scope and the start scope on the stack."); - - // Get the resourceSet's parent - parentScope = this.scopeStack.ParentOfParent; - if (parentScope.State == WriterState.Start || - (parentScope.State == WriterState.ResourceSet && - parentScope.ResourceType != null && - parentScope.ResourceType.TypeKind == EdmTypeKind.Untyped)) - { - // Top-level resourceSet, or resourceSet within an untyped resourceSet. - return null; - } - } - - if (parentScope.State == WriterState.NestedResourceInfoWithContent) - { - // Get the scope of the nested resource info - return (NestedResourceInfoScope)parentScope; - } - - // The parent scope of a resource can only be a resourceSet or an expanded nav link - throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_ParentNestedResourceInfoScope)); - } - } - - /// - /// Validator to validate consistency of collection items (or null if no such validator applies to the current scope). - /// - private ResourceSetWithoutExpectedTypeValidator CurrentResourceSetValidator - { - get - { - Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.Primitive, "CurrentCollectionValidator should only be called while writing a resource."); - - ResourceSetBaseScope resourceSetScope = this.ParentScope as ResourceSetBaseScope; - return resourceSetScope == null ? null : resourceSetScope.ResourceTypeValidator; - } - } - - /// - /// Flushes the write buffer to the underlying stream. - /// - public sealed override void Flush() - { - this.VerifyCanFlush(true); - - // Make sure we switch to writer state Error if an exception is thrown during flushing. - try - { - this.FlushSynchronously(); - } - catch - { - this.EnterScope(WriterState.Error, null); - throw; - } - } - - /// - /// Asynchronously flushes the write buffer to the underlying stream. - /// - /// A task instance that represents the asynchronous operation. - public sealed override Task FlushAsync() - { - this.VerifyCanFlush(false); - - // Make sure we switch to writer state Error if an exception is thrown during flushing. - return this.FlushAsynchronously().FollowOnFaultWith(t => this.EnterScope(WriterState.Error, null)); - } - - /// - /// Start writing a resourceSet. - /// - /// Resource Set/collection to write. - public sealed override void WriteStart(ODataResourceSet resourceSet) - { - this.VerifyCanWriteStartResourceSet(true, resourceSet); - this.WriteStartResourceSetImplementation(resourceSet); - } - - /// - /// Asynchronously start writing a resourceSet. - /// - /// Resource Set/collection to write. - /// A task instance that represents the asynchronous write operation. - public sealed override async Task WriteStartAsync(ODataResourceSet resourceSet) - { - await this.VerifyCanWriteStartResourceSetAsync(false, resourceSet) - .ConfigureAwait(false); - await this.WriteStartResourceSetImplementationAsync(resourceSet) - .ConfigureAwait(false); - } - - /// - /// Start writing a delta resource Set. - /// - /// Resource Set/collection to write. - public sealed override void WriteStart(ODataDeltaResourceSet deltaResourceSet) - { - this.VerifyCanWriteStartDeltaResourceSet(true, deltaResourceSet); - this.WriteStartDeltaResourceSetImplementation(deltaResourceSet); - } - - /// - /// Asynchronously start writing a delta resourceSet. - /// - /// Resource Set/collection to write. - /// A task instance that represents the asynchronous write operation. - public sealed override async Task WriteStartAsync(ODataDeltaResourceSet deltaResourceSet) - { - await this.VerifyCanWriteStartDeltaResourceSetAsync(false, deltaResourceSet) - .ConfigureAwait(false); - await this.WriteStartDeltaResourceSetImplementationAsync(deltaResourceSet) - .ConfigureAwait(false); - } - - /// - /// Start writing a resource. - /// - /// Resource/item to write. - public sealed override void WriteStart(ODataResource resource) - { - this.VerifyCanWriteStartResource(true, resource); - this.WriteStartResourceImplementation(resource); - } - - /// - /// Asynchronously start writing a resource. - /// - /// Resource/item to write. - /// A task instance that represents the asynchronous write operation. - public sealed override async Task WriteStartAsync(ODataResource resource) - { - this.VerifyCanWriteStartResource(false, resource); - await this.WriteStartResourceImplementationAsync(resource) - .ConfigureAwait(false); - } - - /// - /// Start writing a delta deleted resource. - /// - /// The delta deleted resource to write. - public sealed override void WriteStart(ODataDeletedResource deletedResource) - { - this.VerifyCanWriteStartDeletedResource(true, deletedResource); - this.WriteStartDeletedResourceImplementation(deletedResource); - } - - /// - /// Asynchronously write a delta deleted resource. - /// - /// The delta deleted resource to write. - /// A task instance that represents the asynchronous write operation. - public sealed override async Task WriteStartAsync(ODataDeletedResource deletedResource) - { - this.VerifyCanWriteStartDeletedResource(false, deletedResource); - await this.WriteStartDeletedResourceImplementationAsync(deletedResource) - .ConfigureAwait(false); - } - - /// - /// Writing a delta link. - /// - /// The delta link to write. - public override void WriteDeltaLink(ODataDeltaLink deltaLink) - { - this.VerifyCanWriteLink(true, deltaLink); - this.WriteDeltaLinkImplementation(deltaLink); - } - - /// - /// Asynchronously writing a delta link. - /// - /// The delta link to write. - /// A task instance that represents the asynchronous write operation. - public override async Task WriteDeltaLinkAsync(ODataDeltaLink deltaLink) - { - this.VerifyCanWriteLink(false, deltaLink); - await this.WriteDeltaLinkImplementationAsync(deltaLink) - .ConfigureAwait(false); - } - - /// - /// Writing a delta deleted link. - /// - /// The delta link to write. - public override void WriteDeltaDeletedLink(ODataDeltaDeletedLink deltaLink) - { - this.VerifyCanWriteLink(true, deltaLink); - this.WriteDeltaLinkImplementation(deltaLink); - } - - /// - /// Asynchronously writing a delta link. - /// - /// The delta link to write. - /// A task instance that represents the asynchronous write operation. - public override async Task WriteDeltaDeletedLinkAsync(ODataDeltaDeletedLink deltaLink) - { - this.VerifyCanWriteLink(false, deltaLink); - await this.WriteDeltaLinkImplementationAsync(deltaLink) - .ConfigureAwait(false); - } - - /// - /// Write a primitive value within an untyped collection. - /// - /// Primitive value to write. - public sealed override void WritePrimitive(ODataPrimitiveValue primitiveValue) - { - this.VerifyCanWritePrimitive(true, primitiveValue); - this.WritePrimitiveValueImplementation(primitiveValue); - } - - /// - /// Asynchronously write a primitive value. - /// - /// Primitive value to write. - /// A task instance that represents the asynchronous write operation. - public sealed override async Task WritePrimitiveAsync(ODataPrimitiveValue primitiveValue) - { - this.VerifyCanWritePrimitive(false, primitiveValue); - await this.WritePrimitiveValueImplementationAsync(primitiveValue) - .ConfigureAwait(false); - } - - /// Writes a primitive property within a resource. - /// The primitive property to write. - public sealed override void WriteStart(ODataPropertyInfo primitiveProperty) - { - this.VerifyCanWriteProperty(true, primitiveProperty); - this.WriteStartPropertyImplementation(primitiveProperty); - } - - /// Asynchronously write a primitive property within a resource. - /// A task instance that represents the asynchronous write operation. - /// The primitive property to write. - public sealed override async Task WriteStartAsync(ODataPropertyInfo primitiveProperty) - { - this.VerifyCanWriteProperty(false, primitiveProperty); - await this.WriteStartPropertyImplementationAsync(primitiveProperty) - .ConfigureAwait(false); - } - - /// Creates a stream for writing a binary value. - /// A stream to write a binary value to. - public sealed override Stream CreateBinaryWriteStream() - { - this.VerifyCanCreateWriteStream(true); - return this.CreateWriteStreamImplementation(); - } - - /// Asynchronously creates a stream for writing a binary value. - /// A task that represents the asynchronous operation. - /// The value of the TResult parameter contains a to write a binary value to. - public sealed override async Task CreateBinaryWriteStreamAsync() - { - this.VerifyCanCreateWriteStream(false); - return await this.CreateWriteStreamImplementationAsync() - .ConfigureAwait(false); - } - - /// Creates a TextWriter for writing a string value. - /// A TextWriter to write a string value to. - public sealed override TextWriter CreateTextWriter() - { - this.VerifyCanCreateTextWriter(true); - return this.CreateTextWriterImplementation(); - } - - /// Asynchronously creates a for writing a string value. - /// A task that represents the asynchronous operation. - /// The value of the TResult parameter contains a to write a string value to. - public sealed override async Task CreateTextWriterAsync() - { - this.VerifyCanCreateWriteStream(false); - return await this.CreateTextWriterImplementationAsync() - .ConfigureAwait(false); - } - - /// - /// Start writing a nested resource info. - /// - /// Navigation link to write. - public sealed override void WriteStart(ODataNestedResourceInfo nestedResourceInfo) - { - this.VerifyCanWriteStartNestedResourceInfo(true, nestedResourceInfo); - this.WriteStartNestedResourceInfoImplementation(nestedResourceInfo); - } - - - /// - /// Asynchronously start writing a nested resource info. - /// - /// Navigation link to writer. - /// A task instance that represents the asynchronous write operation. - public sealed override async Task WriteStartAsync(ODataNestedResourceInfo nestedResourceInfo) - { - this.VerifyCanWriteStartNestedResourceInfo(false, nestedResourceInfo); - // Currently, no asynchronous operation is involved when commencing with writing a nested resource info - await TaskUtils.GetTaskForSynchronousOperation( - () => this.WriteStartNestedResourceInfoImplementation(nestedResourceInfo)).ConfigureAwait(false); - } - - /// - /// Finish writing a resourceSet/resource/nested resource info. - /// - public sealed override void WriteEnd() - { - this.VerifyCanWriteEnd(true); - this.WriteEndImplementation(); - if (this.CurrentScope.State == WriterState.Completed) - { - // Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state. - this.Flush(); - } - } - - - /// - /// Asynchronously finish writing a resourceSet/resource/nested resource info. - /// - /// A task instance that represents the asynchronous write operation. - public sealed override async Task WriteEndAsync() - { - this.VerifyCanWriteEnd(false); - await this.WriteEndImplementationAsync() - .ConfigureAwait(false); - - if (this.CurrentScope.State == WriterState.Completed) - { - await this.FlushAsync() - .ConfigureAwait(false); - } - } - - /// - /// Writes an entity reference link, which is used to represent binding to an existing resource in a request payload. - /// - /// The entity reference link to write. - /// - /// This method can only be called for writing request messages. The entity reference link must be surrounded - /// by a navigation link written through WriteStart/WriteEnd. - /// The will be ignored in that case and the Uri from the will be used - /// as the binding URL to be written. - /// - public sealed override void WriteEntityReferenceLink(ODataEntityReferenceLink entityReferenceLink) - { - this.VerifyCanWriteEntityReferenceLink(entityReferenceLink, true); - this.WriteEntityReferenceLinkImplementation(entityReferenceLink); - } - - - /// - /// Asynchronously writes an entity reference link, which is used to represent binding to an existing resource in a request payload. - /// - /// The entity reference link to write. - /// A task instance that represents the asynchronous write operation. - /// - /// This method can only be called for writing request messages. The entity reference link must be surrounded - /// by a navigation link written through WriteStart/WriteEnd. - /// The will be ignored in that case and the Uri from the will be used - /// as the binding URL to be written. - /// - public sealed override async Task WriteEntityReferenceLinkAsync(ODataEntityReferenceLink entityReferenceLink) - { - this.VerifyCanWriteEntityReferenceLink(entityReferenceLink, false); - await this.WriteEntityReferenceLinkImplementationAsync(entityReferenceLink) - .ConfigureAwait(false); - } - - /// - /// This method notifies the listener, that an in-stream error is to be written. - /// - /// - /// This listener can choose to fail, if the currently written payload doesn't support in-stream error at this position. - /// If the listener returns, the writer should not allow any more writing, since the in-stream error is the last thing in the payload. - /// - void IODataOutputInStreamErrorListener.OnInStreamError() - { - this.VerifyNotDisposed(); - - // We're in a completed state trying to write an error (we can't write error after the payload was finished as it might - // introduce another top-level element in XML) - if (this.State == WriterState.Completed) - { - throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), WriterState.Error.ToString())); - } - - this.StartPayloadInStartState(); - this.EnterScope(WriterState.Error, this.CurrentScope.Item); - } - - /// - async Task IODataOutputInStreamErrorListener.OnInStreamErrorAsync() - { - this.VerifyNotDisposed(); - - // We're in a completed state trying to write an error (we can't write error after the payload was finished as it might - // introduce another top-level element in XML) - if (this.State == WriterState.Completed) - { - throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), WriterState.Error.ToString())); - } - - await this.StartPayloadInStartStateAsync() - .ConfigureAwait(false); - this.EnterScope(WriterState.Error, this.CurrentScope.Item); - } - - /// - /// This method is called when a stream is requested. It is a no-op. - /// - void IODataStreamListener.StreamRequested() - { - } - - /// - /// This method is called when an async stream is requested. It is a no-op. - /// - /// A task for method called when a stream is requested. - Task IODataStreamListener.StreamRequestedAsync() - { - return TaskUtils.CompletedTask; - } - - /// - /// This method is called when a stream is disposed. - /// - void IODataStreamListener.StreamDisposed() - { - Debug.Assert(this.State == WriterState.Stream || this.State == WriterState.String, "Stream was disposed when not in WriterState.Stream state."); - - // Complete writing the stream - if (this.State == WriterState.Stream) - { - this.EndBinaryStream(); - } - else if (this.State == WriterState.String) - { - this.EndTextWriter(); - } - - this.LeaveScope(); - } - - /// - /// This method is called asynchronously when a stream is disposed. - /// - /// A task that represents the asynchronous operation. - async Task IODataStreamListener.StreamDisposedAsync() - { - Debug.Assert(this.State == WriterState.Stream || this.State == WriterState.String, - "Stream was disposed when not in WriterState.Stream state."); - - // Complete writing the stream - if (this.State == WriterState.Stream) - { - await this.EndBinaryStreamAsync() - .ConfigureAwait(false); - } - else if (this.State == WriterState.String) - { - await this.EndTextWriterAsync() - .ConfigureAwait(false); - } - - await this.LeaveScopeAsync() - .ConfigureAwait(false); - } - - /// - /// Get instance of the parent resource scope - /// - /// - /// The parent resource scope - /// Or null if there is no parent resource scope - /// - protected ResourceScope GetParentResourceScope() - { - ScopeStack scopeStack = new ScopeStack(); - Scope parentResourceScope = null; - - if (this.scopeStack.Count > 0) - { - // pop current scope and push into scope stack - scopeStack.Push(this.scopeStack.Pop()); - } - - while (this.scopeStack.Count > 0) - { - Scope scope = this.scopeStack.Pop(); - scopeStack.Push(scope); - - if (scope is ResourceScope) - { - parentResourceScope = scope; - break; - } - } - - while (scopeStack.Count > 0) - { - Scope scope = scopeStack.Pop(); - this.scopeStack.Push(scope); - } - - return parentResourceScope as ResourceScope; - } - - /// - /// Determines whether a given writer state is considered an error state. - /// - /// The writer state to check. - /// True if the writer state is an error state; otherwise false. - protected static bool IsErrorState(WriterState state) - { - return state == WriterState.Error; - } - - /// - /// Check if the object has been disposed; called from all public API methods. Throws an ObjectDisposedException if the object - /// has already been disposed. - /// - protected abstract void VerifyNotDisposed(); - - /// - /// Flush the output. - /// - protected abstract void FlushSynchronously(); - - - /// - /// Flush the output. - /// - /// Task representing the pending flush operation. - protected abstract Task FlushAsynchronously(); - - /// - /// Start writing an OData payload. - /// - protected abstract void StartPayload(); - - /// - /// Start writing a resource. - /// - /// The resource to write. - protected abstract void StartResource(ODataResource resource); - - /// - /// Finish writing a resource. - /// - /// The resource to write. - protected abstract void EndResource(ODataResource resource); - - /// - /// Start writing a single property. - /// - /// The property to write. - protected virtual void StartProperty(ODataPropertyInfo property) - { - throw new NotImplementedException(); - } - - /// - /// Finish writing a property. - /// - /// The property to write. - protected virtual void EndProperty(ODataPropertyInfo property) - { - throw new NotImplementedException(); - } - - /// - /// Start writing a resourceSet. - /// - /// The resourceSet to write. - protected abstract void StartResourceSet(ODataResourceSet resourceSet); - - /// - /// Start writing a delta resource set. - /// - /// The delta resource set to write. - protected virtual void StartDeltaResourceSet(ODataDeltaResourceSet deltaResourceSet) - { - throw new NotImplementedException(); - } - - /// - /// Start writing a deleted resource. - /// - /// The deleted entry to write. - protected virtual void StartDeletedResource(ODataDeletedResource deletedEntry) - { - throw new NotImplementedException(); - } - - /// - /// Write a delta link or delta deleted link. - /// - /// The deleted entry to write. - protected virtual void StartDeltaLink(ODataDeltaLinkBase deltaLink) - { - throw new NotImplementedException(); - } - - /// - /// Create a stream to write a binary value. - /// - /// A stream for writing the binary value. - protected virtual Stream StartBinaryStream() - { - throw new NotImplementedException(); - } - - /// - /// Finish writing a stream. - /// - protected virtual void EndBinaryStream() - { - throw new NotImplementedException(); - } - - /// - /// Create a TextWriter to write a string value. - /// - /// A TextWriter for writing the string value. - protected virtual TextWriter StartTextWriter() - { - throw new NotImplementedException(); - } - - /// - /// Finish writing a string value. - /// - protected virtual void EndTextWriter() - { - throw new NotImplementedException(); - } - - /// - /// Finish writing an OData payload. - /// - protected abstract void EndPayload(); - - /// - /// Finish writing a resourceSet. - /// - /// The resourceSet to write. - protected abstract void EndResourceSet(ODataResourceSet resourceSet); - - /// - /// Finish writing a delta resource set. - /// - /// The delta resource set to write. - protected virtual void EndDeltaResourceSet(ODataDeltaResourceSet deltaResourceSet) - { - throw new NotImplementedException(); - } - - /// - /// Finish writing a deleted resource. - /// - /// The delta resource set to write. - protected virtual void EndDeletedResource(ODataDeletedResource deletedResource) - { - throw new NotImplementedException(); - } - - /// - /// Write a primitive value within an untyped collection. - /// - /// The primitive value to write. - protected virtual void WritePrimitiveValue(ODataPrimitiveValue primitiveValue) - { - throw new NotImplementedException(); - } - - /// - /// Write a deferred (non-expanded) nested resource info. - /// - /// The nested resource info to write. - protected abstract void WriteDeferredNestedResourceInfo(ODataNestedResourceInfo nestedResourceInfo); - - /// - /// Start writing a nested resource info with content. - /// - /// The nested resource info to write. - protected abstract void StartNestedResourceInfoWithContent(ODataNestedResourceInfo nestedResourceInfo); - - /// - /// Finish writing a nested resource info with content. - /// - /// The nested resource info to write. - protected abstract void EndNestedResourceInfoWithContent(ODataNestedResourceInfo nestedResourceInfo); - - /// - /// Write an entity reference link into a navigation link content. - /// - /// The parent navigation link which is being written around the entity reference link. - /// The entity reference link to write. - protected abstract void WriteEntityReferenceInNavigationLinkContent(ODataNestedResourceInfo parentNestedResourceInfo, ODataEntityReferenceLink entityReferenceLink); - - /// - /// Create a new resource set scope. - /// - /// The resource set for the new scope. - /// The navigation source we are going to write resource set for. - /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). - /// true if the content of the scope to create should not be written. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - /// true if the resource set is for an undeclared property - /// The newly create scope. - protected abstract ResourceSetScope CreateResourceSetScope(ODataResourceSet resourceSet, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared); - - /// - /// Create a new delta resource set scope. - /// - /// The delta resource set for the new scope. - /// The navigation source we are going to write resource set for. - /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). - /// true if the content of the scope to create should not be written. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - /// true if the resource set is for an undeclared property - /// The newly create scope. - protected virtual DeltaResourceSetScope CreateDeltaResourceSetScope(ODataDeltaResourceSet deltaResourceSet, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared) - { - throw new NotImplementedException(); - } - - /// - /// Create a new resource scope. - /// - /// The resource for the new scope. - /// The navigation source we are going to write resource set for. - /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used). - /// true if the content of the scope to create should not be written. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - /// true if the resource is for an undeclared property - /// The newly create scope. - protected abstract ResourceScope CreateResourceScope(ODataResource resource, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared); - - /// - /// Create a new resource scope. - /// - /// The (deleted) resource for the new scope. - /// The navigation source we are going to write resource set for. - /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used). - /// true if the content of the scope to create should not be written. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - /// true if the resource is for an undeclared property - /// The newly create scope. - protected virtual DeletedResourceScope CreateDeletedResourceScope(ODataDeletedResource resource, IEdmNavigationSource navigationSource, IEdmEntityType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared) - { - throw new NotImplementedException(); - } - - /// - /// Create a new property scope. - /// - /// The property for the new scope. - /// The navigation source. - /// The structured type for the resource containing the property to be written. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - /// The newly created property scope. - protected virtual PropertyInfoScope CreatePropertyInfoScope(ODataPropertyInfo property, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) - { - throw new NotImplementedException(); - } - - /// - /// Create a new delta link scope. - /// - /// The link for the new scope. - /// The navigation source we are going to write entities for. - /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). - /// The selected properties of this scope. - /// The ODataUri info of this scope. - /// The newly create scope. - protected virtual DeltaLinkScope CreateDeltaLinkScope(ODataDeltaLinkBase link, IEdmNavigationSource navigationSource, IEdmEntityType entityType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) - { - throw new NotImplementedException(); - } - - /// - /// Gets the serialization info for the given resource. - /// - /// The resource to get the serialization info for. - /// The serialization info for the given resource. - protected ODataResourceSerializationInfo GetResourceSerializationInfo(ODataResourceBase resource) - { - // Need to check for null for the resource since we can be writing a null reference to a navigation property. - ODataResourceSerializationInfo serializationInfo = resource == null ? null : resource.SerializationInfo; - - // Always try to use the serialization info from the resource first. If it is not found on the resource, use the one inherited from the parent resourceSet. - // Note that we don't try to guard against inconsistent serialization info between entries and their parent resourceSet. - if (serializationInfo != null) - { - return serializationInfo; - } - - ODataResourceSetBase resourceSet = this.CurrentScope.Item as ODataResourceSetBase; - if (resourceSet != null) - { - return resourceSet.SerializationInfo; - } - - return null; - } - - /// - /// Gets the serialization info for the given delta link. - /// - /// The resource to get the serialization info for. - /// The serialization info for the given resource. - protected ODataResourceSerializationInfo GetLinkSerializationInfo(ODataItem item) - { - Debug.Assert(item != null, "item != null"); - - ODataDeltaSerializationInfo deltaSerializationInfo = null; - ODataResourceSerializationInfo resourceSerializationInfo = null; - - ODataDeltaLink deltaLink = item as ODataDeltaLink; - if (deltaLink != null) - { - deltaSerializationInfo = deltaLink.SerializationInfo; - } - - ODataDeltaDeletedLink deltaDeletedLink = item as ODataDeltaDeletedLink; - if (deltaDeletedLink != null) - { - deltaSerializationInfo = deltaDeletedLink.SerializationInfo; - } - - if (deltaSerializationInfo == null) - { - DeltaResourceSetScope parentDeltaResourceSetScope = this.CurrentScope as DeltaResourceSetScope; - if (parentDeltaResourceSetScope != null) - { - ODataDeltaResourceSet resourceSet = (ODataDeltaResourceSet)parentDeltaResourceSetScope.Item; - Debug.Assert(resourceSet != null, "resourceSet != null"); - - ODataResourceSerializationInfo deltaSetSerializationInfo = resourceSet.SerializationInfo; - if (deltaSetSerializationInfo != null) - { - resourceSerializationInfo = deltaSetSerializationInfo; - } - } - } - else - { - resourceSerializationInfo = new ODataResourceSerializationInfo() - { - NavigationSourceName = deltaSerializationInfo.NavigationSourceName - }; - } - - return resourceSerializationInfo; - } - - /// - /// Creates a new nested resource info scope. - /// - /// The writer state for the new scope. - /// The nested resource info for the new scope. - /// The navigation source we are going to write entities for. - /// The type for the items in the resourceSet to be written (or null if the resource set base type should be used). - /// true if the content of the scope to create should not be written. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - /// The newly created nested resource info scope. - protected virtual NestedResourceInfoScope CreateNestedResourceInfoScope( - WriterState writerState, - ODataNestedResourceInfo navLink, - IEdmNavigationSource navigationSource, - IEdmType itemType, - bool skipWriting, - SelectedPropertiesNode selectedProperties, - ODataUri odataUri) - { - Debug.Assert(this.CurrentScope != null, "Creating a nested resource info scope with a null parent scope."); - return new NestedResourceInfoScope(writerState, navLink, navigationSource, itemType, skipWriting, selectedProperties, odataUri, this.CurrentScope); - } - - /// - /// Place where derived writers can perform custom steps before the resource is written, at the beginning of WriteStartEntryImplementation. - /// - /// The ResourceScope. - /// Resource to write. - /// True if writing response. - /// The selected properties of this scope. - protected virtual void PrepareResourceForWriteStart(ResourceScope resourceScope, ODataResource resource, bool writingResponse, SelectedPropertiesNode selectedProperties) - { - // No-op Atom and Verbose JSON. The JSON Light writer will override this method and inject the appropriate metadata builder - // into the resource before writing. - // Actually we can inject the metadata builder in here and - // remove virtual from this method. - } - - /// - /// Place where derived writers can perform custom steps before the deleted resource is written, at the beginning of WriteStartEntryImplementation. - /// - /// The ResourceScope. - /// Resource to write. - /// True if writing response. - /// The selected properties of this scope. - protected virtual void PrepareDeletedResourceForWriteStart(DeletedResourceScope resourceScope, ODataDeletedResource deletedResource, bool writingResponse, SelectedPropertiesNode selectedProperties) - { - // No-op Atom and Verbose JSON. The JSON Light writer will override this method and inject the appropriate metadata builder - // into the resource before writing. - // Actually we can inject the metadata builder in here and - // remove virtual from this method. - } - - /// - /// Gets the type of the resource and validates it against the model. - /// - /// The resource to get the type for. - /// The validated structured type. - protected IEdmStructuredType GetResourceType(ODataResourceBase resource) - { - return TypeNameOracle.ResolveAndValidateTypeFromTypeName( - this.outputContext.Model, - this.CurrentScope.ResourceType, - resource.TypeName, - this.WriterValidator); - } - - /// - /// Gets the element type of the resource set and validates it against the model. - /// - /// The resource set to get the element type for. - /// The validated structured element type. - protected IEdmStructuredType GetResourceSetType(ODataResourceSetBase resourceSet) - { - return TypeNameOracle.ResolveAndValidateTypeFromTypeName( - this.outputContext.Model, - this.CurrentScope.ResourceType, - EdmLibraryExtensions.GetCollectionItemTypeName(resourceSet.TypeName), - this.WriterValidator); - } - - /// - /// Validates that the ODataResourceSet.DeltaLink is null for the given expanded resourceSet. - /// - /// The expanded resourceSet in question. - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "An instance field is used in a debug assert.")] - protected void ValidateNoDeltaLinkForExpandedResourceSet(ODataResourceSet resourceSet) - { - Debug.Assert(resourceSet != null, "resourceSet != null"); - Debug.Assert( - this.ParentNestedResourceInfo != null && (!this.ParentNestedResourceInfo.IsCollection.HasValue || this.ParentNestedResourceInfo.IsCollection.Value == true), - "This should only be called when writing an expanded resourceSet."); - - if (resourceSet.DeltaLink != null) - { - throw new ODataException(Strings.ODataWriterCore_DeltaLinkNotSupportedOnExpandedResourceSet); - } - } - - /// - /// Asynchronously start writing an OData payload. - /// - /// A task that represents the asynchronous write operation. - protected abstract Task StartPayloadAsync(); - - /// - /// Asynchronously finish writing an OData payload. - /// - /// A task that represents the asynchronous write operation. - protected abstract Task EndPayloadAsync(); - - /// - /// Asynchronously start writing a resource. - /// - /// The resource to write. - /// A task that represents the asynchronous write operation. - protected abstract Task StartResourceAsync(ODataResource resource); - - /// - /// Asynchronously finish writing a resource. - /// - /// The resource to write. - /// A task that represents the asynchronous write operation. - protected abstract Task EndResourceAsync(ODataResource resource); - - /// - /// Asynchronously start writing a single property. - /// - /// The property to write. - /// A task that represents the asynchronous write operation. - protected virtual Task StartPropertyAsync(ODataPropertyInfo property) - { - throw new NotImplementedException(); - } - - /// - /// Asynchronously finish writing a property. - /// - /// The property to write. - /// A task that represents the asynchronous write operation. - protected virtual Task EndPropertyAsync(ODataPropertyInfo property) - { - throw new NotImplementedException(); - } - - /// - /// Asynchronously start writing a resourceSet. - /// - /// The resourceSet to write. - /// A task that represents the asynchronous write operation. - protected abstract Task StartResourceSetAsync(ODataResourceSet resourceSet); - - /// - /// Asynchronously finish writing a resourceSet. - /// - /// The resourceSet to write. - /// A task that represents the asynchronous write operation. - protected abstract Task EndResourceSetAsync(ODataResourceSet resourceSet); - - /// - /// Asynchronously start writing a delta resource set. - /// - /// The delta resource set to write. - /// A task that represents the asynchronous write operation. - protected virtual Task StartDeltaResourceSetAsync(ODataDeltaResourceSet deltaResourceSet) - { - throw new NotImplementedException(); - } - - /// - /// Asynchronously finish writing a delta resource set. - /// - /// The delta resource set to write. - /// A task that represents the asynchronous write operation. - protected virtual Task EndDeltaResourceSetAsync(ODataDeltaResourceSet deltaResourceSet) - { - throw new NotImplementedException(); - } - - /// - /// Asynchronously start writing a deleted resource. - /// - /// The deleted entry to write. - /// A task that represents the asynchronous write operation. - protected virtual Task StartDeletedResourceAsync(ODataDeletedResource deletedEntry) - { - throw new NotImplementedException(); - } - - /// - /// Asynchronously finish writing a deleted resource. - /// - /// The delta resource set to write. - /// A task that represents the asynchronous write operation. - protected virtual Task EndDeletedResourceAsync(ODataDeletedResource deletedResource) - { - throw new NotImplementedException(); - } - - /// - /// Asynchronously write a delta link or delta deleted link. - /// - /// The deleted entry to write. - /// A task that represents the asynchronous write operation. - protected virtual Task StartDeltaLinkAsync(ODataDeltaLinkBase deltaLink) - { - throw new NotImplementedException(); - } - - /// - /// Asynchronously create a to write a binary value. - /// - /// A task that represents the asynchronous write operation. - /// The value of the TResult parameter contains the for writing the binary value. - protected virtual Task StartBinaryStreamAsync() - { - throw new NotImplementedException(); - } - - /// - /// Asynchronously finish writing a stream. - /// - /// A task that represents the asynchronous write operation. - protected virtual Task EndBinaryStreamAsync() - { - throw new NotImplementedException(); - } - - /// - /// Asynchronously create a to write a string value. - /// - /// A task that represents the asynchronous operation. - /// The value of the TResult parameter contains the for writing a string value. - protected virtual Task StartTextWriterAsync() - { - throw new NotImplementedException(); - } - - /// - /// Asynchronously finish writing a string value. - /// - /// A task that represents the asynchronous write operation. - protected virtual Task EndTextWriterAsync() - { - throw new NotImplementedException(); - } - - /// - /// Asynchronously write a primitive value within an untyped collection. - /// - /// The primitive value to write. - /// A task that represents the asynchronous write operation. - protected virtual Task WritePrimitiveValueAsync(ODataPrimitiveValue primitiveValue) - { - throw new NotImplementedException(); - } - - /// - /// Asynchronously write a deferred (non-expanded) nested resource info. - /// - /// The nested resource info to write. - /// A task that represents the asynchronous write operation. - protected abstract Task WriteDeferredNestedResourceInfoAsync(ODataNestedResourceInfo nestedResourceInfo); - - /// - /// Asynchronously start writing a nested resource info with content. - /// - /// The nested resource info to write. - /// A task that represents the asynchronous write operation. - protected abstract Task StartNestedResourceInfoWithContentAsync(ODataNestedResourceInfo nestedResourceInfo); - - /// - /// Asynchronously finish writing a nested resource info with content. - /// - /// The nested resource info to write. - /// A task that represents the asynchronous write operation. - protected abstract Task EndNestedResourceInfoWithContentAsync(ODataNestedResourceInfo nestedResourceInfo); - - /// - /// Asynchronously write an entity reference link into a navigation link content. - /// - /// The parent navigation link which is being written around the entity reference link. - /// The entity reference link to write. - /// A task that represents the asynchronous write operation. - protected abstract Task WriteEntityReferenceInNavigationLinkContentAsync( - ODataNestedResourceInfo parentNestedResourceInfo, - ODataEntityReferenceLink entityReferenceLink); - - /// - /// Place where derived writers can perform custom steps before the resource is written, - /// at the beginning of WriteStartAsync(ODataResource). - /// - /// The ResourceScope. - /// Resource to write. - /// True if writing response. - /// The selected properties of this scope. - protected virtual Task PrepareResourceForWriteStartAsync( - ResourceScope resourceScope, - ODataResource resource, - bool writingResponse, - SelectedPropertiesNode selectedProperties) - { - // No-op Atom and Verbose JSON. - // ODataJsonLightWriter will override this method and inject the appropriate metadata builder - // into the resource before writing. - return TaskUtils.CompletedTask; - } - - /// - /// Place where derived writers can perform custom steps before the deleted resource is written, - /// at the beginning of WriteStartAsync(ODataDeletedResource). - /// - /// The ResourceScope. - /// Resource to write. - /// True if writing response. - /// The selected properties of this scope. - protected virtual Task PrepareDeletedResourceForWriteStartAsync( - DeletedResourceScope resourceScope, - ODataDeletedResource deletedResource, - bool writingResponse, - SelectedPropertiesNode selectedProperties) - { - // No-op Atom and Verbose JSON. - // ODataJsonLightWriter will override this method and inject the appropriate metadata builder - // into the resource before writing. - return TaskUtils.CompletedTask; - } - - /// - /// Verifies that calling WriteStart resourceSet is valid. - /// - /// true if the call is to be synchronous; false otherwise. - /// Resource Set/collection to write. - private void VerifyCanWriteStartResourceSet(bool synchronousCall, ODataResourceSet resourceSet) - { - ExceptionUtils.CheckArgumentNotNull(resourceSet, "resourceSet"); - - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - this.StartPayloadInStartState(); - } - - /// - /// Start writing a resourceSet - implementation of the actual functionality. - /// - /// The resource set to write. - private void WriteStartResourceSetImplementation(ODataResourceSet resourceSet) - { - this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.ResourceSet, resourceSet); - this.EnterScope(WriterState.ResourceSet, resourceSet); - - if (!this.SkipWriting) - { - this.InterceptException( - (thisParam, resourceSetParam) => - { - // Verify query count - if (resourceSetParam.Count.HasValue) - { - // Check that Count is not set for requests - if (!thisParam.outputContext.WritingResponse) - { - thisParam.ThrowODataException(Strings.ODataWriterCore_QueryCountInRequest, resourceSetParam); - } - - // Verify version requirements - } - - thisParam.StartResourceSet(resourceSetParam); - }, resourceSet); - } - } - - /// - /// Verifies that calling WriteStart deltaResourceSet is valid. - /// - /// true if the call is to be synchronous; false otherwise. - /// Resource Set/collection to write. - private void VerifyCanWriteStartDeltaResourceSet(bool synchronousCall, ODataDeltaResourceSet deltaResourceSet) - { - ExceptionUtils.CheckArgumentNotNull(deltaResourceSet, "deltaResourceSet"); - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - this.StartPayloadInStartState(); - } - - /// - /// Start writing a delta resource set - implementation of the actual functionality. - /// - /// The delta resource Set to write. - private void WriteStartDeltaResourceSetImplementation(ODataDeltaResourceSet deltaResourceSet) - { - this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.ResourceSet, deltaResourceSet); - this.EnterScope(WriterState.DeltaResourceSet, deltaResourceSet); - - this.InterceptException( - (thisParam, deltaResourceSetParam) => - { - // Check that links are not set for requests - if (!thisParam.outputContext.WritingResponse) - { - if (deltaResourceSetParam.NextPageLink != null) - { - thisParam.ThrowODataException(Strings.ODataWriterCore_QueryNextLinkInRequest, deltaResourceSetParam); - } - - if (deltaResourceSetParam.DeltaLink != null) - { - thisParam.ThrowODataException(Strings.ODataWriterCore_QueryDeltaLinkInRequest, deltaResourceSetParam); - } - } - - thisParam.StartDeltaResourceSet(deltaResourceSetParam); - }, deltaResourceSet); - } - - /// - /// Verifies that calling WriteStart resource is valid. - /// - /// true if the call is to be synchronous; false otherwise. - /// Resource/item to write. - private void VerifyCanWriteStartResource(bool synchronousCall, ODataResource resource) - { - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - } - - /// - /// Verifies that calling WriteDeletedResource is valid. - /// - /// true if the call is to be synchronous; false otherwise. - /// Resource/item to write. - private void VerifyCanWriteStartDeletedResource(bool synchronousCall, ODataDeletedResource resource) - { - ExceptionUtils.CheckArgumentNotNull(resource, "resource"); - - this.VerifyWritingDelta(); - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - } - - /// - /// Start writing a resource - implementation of the actual functionality. - /// - /// Resource/item to write. - private void WriteStartResourceImplementation(ODataResource resource) - { - this.StartPayloadInStartState(); - this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.Resource, resource); - this.EnterScope(WriterState.Resource, resource); - if (!this.SkipWriting) - { - this.IncreaseResourceDepth(); - this.InterceptException( - (thisParam, resourceParam) => - { - if (resourceParam != null) - { - ResourceScope resourceScope = (ResourceScope)thisParam.CurrentScope; - thisParam.ValidateResourceForResourceSet(resourceParam, resourceScope); - thisParam.PrepareResourceForWriteStart( - resourceScope, - resourceParam, - thisParam.outputContext.WritingResponse, - resourceScope.SelectedProperties); - } - - thisParam.StartResource(resourceParam); - }, resource); - } - } - - /// - /// Start writing a delta deleted resource - implementation of the actual functionality. - /// - /// Resource/item to write. - private void WriteStartDeletedResourceImplementation(ODataDeletedResource resource) - { - Debug.Assert(resource != null, "resource != null"); - - this.StartPayloadInStartState(); - this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.Resource, resource); - this.EnterScope(WriterState.DeletedResource, resource); - this.IncreaseResourceDepth(); - - this.InterceptException( - (thisParam, resourceParam) => - { - DeletedResourceScope resourceScope = thisParam.CurrentScope as DeletedResourceScope; - thisParam.ValidateResourceForResourceSet(resourceParam, resourceScope); - thisParam.PrepareDeletedResourceForWriteStart( - resourceScope, - resourceParam, - thisParam.outputContext.WritingResponse, - resourceScope.SelectedProperties); - thisParam.StartDeletedResource(resourceParam); - }, resource); - } - - /// - /// Verifies that calling WriteStart for a property is valid. - /// - /// true if the call is to be synchronous; false otherwise. - /// Primitive property to write. - private void VerifyCanWriteProperty(bool synchronousCall, ODataPropertyInfo property) - { - ExceptionUtils.CheckArgumentNotNull(property, "property"); - - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - } - - /// - /// Start writing a property - implementation of the actual functionality. - /// - /// Property to write. - private void WriteStartPropertyImplementation(ODataPropertyInfo property) - { - this.EnterScope(WriterState.Property, property); - if (!this.SkipWriting) - { - this.InterceptException( - (thisParam, propertyParam) => - { - thisParam.StartProperty(propertyParam); - if (propertyParam is ODataProperty) - { - PropertyInfoScope scope = thisParam.CurrentScope as PropertyInfoScope; - Debug.Assert(scope != null, "Scope for ODataPropertyInfo is not ODataPropertyInfoScope"); - scope.ValueWritten = true; - } - }, property); - } - } - - /// - /// Start writing a delta link or delta deleted link - implementation of the actual functionality. - /// - /// Delta (deleted) link to write. - private void WriteDeltaLinkImplementation(ODataDeltaLinkBase deltaLink) - { - this.EnterScope(deltaLink is ODataDeltaLink ? WriterState.DeltaLink : WriterState.DeltaDeletedLink, deltaLink); - this.StartDeltaLink(deltaLink); - this.WriteEnd(); - } - - /// - /// Start writing a delta link or delta deleted link - implementation of the actual functionality. - /// - /// Delta (deleted) link to write. - /// The task. - private async Task WriteDeltaLinkImplementationAsync(ODataDeltaLinkBase deltaLink) - { - EnterScope(deltaLink is ODataDeltaLink ? WriterState.DeltaLink : WriterState.DeltaDeletedLink, deltaLink); - await this.StartDeltaLinkAsync(deltaLink) - .ConfigureAwait(false); - await this.WriteEndAsync() - .ConfigureAwait(false); - } - - /// - /// Verifies that calling WriteStart nested resource info is valid. - /// - /// true if the call is to be synchronous; false otherwise. - /// Navigation link to write. - private void VerifyCanWriteStartNestedResourceInfo(bool synchronousCall, ODataNestedResourceInfo nestedResourceInfo) - { - ExceptionUtils.CheckArgumentNotNull(nestedResourceInfo, "nestedResourceInfo"); - - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - } - - /// - /// Start writing a nested resource info - implementation of the actual functionality. - /// - /// Navigation link to write. - private void WriteStartNestedResourceInfoImplementation(ODataNestedResourceInfo nestedResourceInfo) - { - this.EnterScope(WriterState.NestedResourceInfo, nestedResourceInfo); - - // If the parent resource has a metadata builder, use that metadatabuilder on the nested resource info as well. - Debug.Assert(this.scopeStack.Parent != null, "Navigation link scopes must have a parent scope."); - Debug.Assert(this.scopeStack.Parent.Item is ODataResourceBase, "The parent of a nested resource info scope should always be a resource"); - ODataResourceBase parentResource = (ODataResourceBase)this.scopeStack.Parent.Item; - if (parentResource.MetadataBuilder != null) - { - nestedResourceInfo.MetadataBuilder = parentResource.MetadataBuilder; - } - } - - /// - /// Verifies that calling WritePrimitive is valid. - /// - /// true if the call is to be synchronous; false otherwise. - /// Primitive value to write. - private void VerifyCanWritePrimitive(bool synchronousCall, ODataPrimitiveValue primitiveValue) - { - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - } - - /// - /// Write primitive value - implementation of the actual functionality. - /// - /// Primitive value to write. - private void WritePrimitiveValueImplementation(ODataPrimitiveValue primitiveValue) - { - this.InterceptException( - (thisParam, primitiveValueParam) => - { - thisParam.EnterScope(WriterState.Primitive, primitiveValueParam); - if (!(thisParam.CurrentResourceSetValidator == null) && primitiveValueParam != null) - { - Debug.Assert(primitiveValueParam.Value != null, "PrimitiveValue.Value should never be null!"); - IEdmType itemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primitiveValueParam.Value.GetType()).Definition; - thisParam.CurrentResourceSetValidator.ValidateResource(itemType); - } - - thisParam.WritePrimitiveValue(primitiveValueParam); - thisParam.WriteEnd(); - }, primitiveValue); - } - - /// - /// Write primitive value asynchronously - implementation of the actual functionality. - /// - /// Primitive value to write. - /// The task. - private async Task WritePrimitiveValueImplementationAsync(ODataPrimitiveValue primitiveValue) - { - EnterScope(WriterState.Primitive, primitiveValue); - - await InterceptExceptionAsync( - async (thisParam, primiteValueParam) => - { - if (!(CurrentResourceSetValidator == null) && primiteValueParam != null) - { - Debug.Assert(primiteValueParam.Value != null, "PrimitiveValue.Value should never be null!"); - IEdmType itemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primiteValueParam.Value.GetType()).Definition; - CurrentResourceSetValidator.ValidateResource(itemType); - } - - await thisParam.WritePrimitiveValueAsync(primiteValueParam) - .ConfigureAwait(false); - await thisParam.WriteEndAsync() - .ConfigureAwait(false); - }, primitiveValue).ConfigureAwait(false); - } - - /// - /// Verifies that calling CreateWriteStream is valid. - /// - /// true if the call is to be synchronous; false otherwise. - private void VerifyCanCreateWriteStream(bool synchronousCall) - { - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - } - - /// - /// Create a write stream - implementation of the actual functionality. - /// - /// A stream for writing the binary value. - private Stream CreateWriteStreamImplementation() - { - this.EnterScope(WriterState.Stream, null); - return new ODataNotificationStream(this.StartBinaryStream(), this); - } - - /// - /// Verifies that calling CreateTextWriter is valid. - /// - /// true if the call is to be synchronous; false otherwise. - private void VerifyCanCreateTextWriter(bool synchronousCall) - { - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - } - - /// - /// Create a text writer - implementation of the actual functionality. - /// - /// A TextWriter for writing the string value. - private TextWriter CreateTextWriterImplementation() - { - this.EnterScope(WriterState.String, null); - return new ODataNotificationWriter(this.StartTextWriter(), this); - } - - /// - /// Verify that calling WriteEnd is valid. - /// - /// true if the call is to be synchronous; false otherwise. - private void VerifyCanWriteEnd(bool synchronousCall) - { - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - } - - /// - /// Finish writing a resourceSet/resource/nested resource info. - /// - private void WriteEndImplementation() - { - this.InterceptException( - (thisParam) => - { - bool wasenableDelta; - Scope currentScope = thisParam.CurrentScope; - - switch (currentScope.State) - { - case WriterState.Resource: - if (!thisParam.SkipWriting) - { - ODataResource resource = (ODataResource)currentScope.Item; - - thisParam.EndResource(resource); - thisParam.DecreaseResourceDepth(); - } - - break; - case WriterState.DeletedResource: - if (!thisParam.SkipWriting) - { - ODataDeletedResource resource = (ODataDeletedResource)currentScope.Item; - - thisParam.EndDeletedResource(resource); - thisParam.DecreaseResourceDepth(); - } - - break; - case WriterState.ResourceSet: - if (!thisParam.SkipWriting) - { - ODataResourceSet resourceSet = (ODataResourceSet)currentScope.Item; - WriterValidationUtils.ValidateResourceSetAtEnd(resourceSet, !thisParam.outputContext.WritingResponse); - thisParam.EndResourceSet(resourceSet); - } - - break; - case WriterState.DeltaLink: - case WriterState.DeltaDeletedLink: - break; - case WriterState.DeltaResourceSet: - if (!thisParam.SkipWriting) - { - ODataDeltaResourceSet deltaResourceSet = (ODataDeltaResourceSet)currentScope.Item; - WriterValidationUtils.ValidateDeltaResourceSetAtEnd(deltaResourceSet, !thisParam.outputContext.WritingResponse); - thisParam.EndDeltaResourceSet(deltaResourceSet); - } - - break; - case WriterState.NestedResourceInfo: - if (!thisParam.outputContext.WritingResponse) - { - throw new ODataException(Strings.ODataWriterCore_DeferredLinkInRequest); - } - - if (!thisParam.SkipWriting) - { - ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item; - thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(link); - thisParam.WriteDeferredNestedResourceInfo(link); - - thisParam.MarkNestedResourceInfoAsProcessed(link); - } - - break; - case WriterState.NestedResourceInfoWithContent: - if (!thisParam.SkipWriting) - { - ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item; - thisParam.EndNestedResourceInfoWithContent(link); - - thisParam.MarkNestedResourceInfoAsProcessed(link); - } - - break; - case WriterState.Property: - { - ODataPropertyInfo property = (ODataPropertyInfo)currentScope.Item; - thisParam.EndProperty(property); - } - - break; - case WriterState.Primitive: - // WriteEnd for WriterState.Primitive is a no-op; just leave scope - break; - case WriterState.Stream: - case WriterState.String: - throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed); - case WriterState.Start: // fall through - case WriterState.Completed: // fall through - case WriterState.Error: // fall through - throw new ODataException(Strings.ODataWriterCore_WriteEndCalledInInvalidState(currentScope.State.ToString())); - default: - throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_WriteEnd_UnreachableCodePath)); - } - - thisParam.LeaveScope(); - }); - } - - /// - /// Marks the navigation currently being written as processed in the parent entity's metadata builder. - /// This is needed so that at the end of writing the resource we can query for all the unwritten navigation properties - /// defined on the entity type and write out their metadata in fullmetadata mode. - /// - /// The nested resource info being written. - private void MarkNestedResourceInfoAsProcessed(ODataNestedResourceInfo link) - { - Debug.Assert( - this.CurrentScope.State == WriterState.NestedResourceInfo || this.CurrentScope.State == WriterState.NestedResourceInfoWithContent, - "This method should only be called when we're writing a nested resource info."); - - ODataResourceBase parent = (ODataResourceBase)this.scopeStack.Parent.Item; - Debug.Assert(parent.MetadataBuilder != null, "parent.MetadataBuilder != null"); - parent.MetadataBuilder.MarkNestedResourceInfoProcessed(link.Name); - } - - /// - /// Verifies that calling WriteEntityReferenceLink is valid. - /// - /// The entity reference link to write. - /// true if the call is to be synchronous; false otherwise. - private void VerifyCanWriteEntityReferenceLink(ODataEntityReferenceLink entityReferenceLink, bool synchronousCall) - { - ExceptionUtils.CheckArgumentNotNull(entityReferenceLink, "entityReferenceLink"); - - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - } - - /// - /// Verifies that calling Write(Deleted)DeltaLink is valid. - /// - /// true if the call is to be synchronous; false otherwise. - /// Delta link to write. - private void VerifyCanWriteLink(bool synchronousCall, ODataDeltaLinkBase deltaLink) - { - this.VerifyWritingDelta(); - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - - ExceptionUtils.CheckArgumentNotNull(deltaLink, "deltaLink"); - } - - /// - /// Write an entity reference link. - /// - /// The entity reference link to write. - private void WriteEntityReferenceLinkImplementation(ODataEntityReferenceLink entityReferenceLink) - { - Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null"); - - this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.EntityReferenceLink, null); - Debug.Assert( - this.CurrentScope.Item is ODataNestedResourceInfo || this.ParentNestedResourceInfoScope.Item is ODataNestedResourceInfo, - "The CheckForNestedResourceInfoWithContent should have verified that entity reference link can only be written inside a nested resource info."); - - if (!this.SkipWriting) - { - this.InterceptException( - (thisParam, entityReferenceLinkParam) => - { - WriterValidationUtils.ValidateEntityReferenceLink(entityReferenceLinkParam); - - ODataNestedResourceInfo nestedInfo = thisParam.CurrentScope.Item as ODataNestedResourceInfo; - if (nestedInfo == null) - { - NestedResourceInfoScope nestedResourceInfoScope = thisParam.ParentNestedResourceInfoScope; - Debug.Assert(nestedResourceInfoScope != null); - nestedInfo = (ODataNestedResourceInfo)nestedResourceInfoScope.Item; - } - - thisParam.WriteEntityReferenceInNavigationLinkContent(nestedInfo, entityReferenceLinkParam); - }, entityReferenceLink); - } - } - - /// - /// Verifies that calling Flush is valid. - /// - /// true if the call is to be synchronous; false otherwise. - private void VerifyCanFlush(bool synchronousCall) - { - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - } - - /// - /// Verifies that a call is allowed to the writer. - /// - /// true if the call is to be synchronous; false otherwise. - private void VerifyCallAllowed(bool synchronousCall) - { - if (synchronousCall) - { - if (!this.outputContext.Synchronous) - { - throw new ODataException(Strings.ODataWriterCore_SyncCallOnAsyncWriter); - } - } - else - { - if (this.outputContext.Synchronous) - { - throw new ODataException(Strings.ODataWriterCore_AsyncCallOnSyncWriter); - } - } - } - - /// - /// Verifies that the writer is writing within a delta resource set. - /// - private void VerifyWritingDelta() - { - if (!this.CurrentScope.EnableDelta) - { - throw new ODataException(Strings.ODataWriterCore_CannotWriteDeltaWithResourceSetWriter); - } - } - - /// - /// Enters the 'Error' state and then throws an ODataException with the specified error message. - /// - /// The error message for the exception. - /// The OData item to associate with the 'Error' state. - private void ThrowODataException(string errorMessage, ODataItem item) - { - this.EnterScope(WriterState.Error, item); - throw new ODataException(errorMessage); - } - - /// - /// Checks whether we are currently writing the first top-level element; if so call StartPayload - /// - private void StartPayloadInStartState() - { - if (this.State == WriterState.Start) - { - this.InterceptException((thisParam) => thisParam.StartPayload()); - } - } - - /// - /// Checks whether we are currently writing a nested resource info and switches to NestedResourceInfoWithContent state if we do. - /// - /// - /// What kind of payload kind is being written as the content of a nested resource info. - /// Only Resource Set, Resource or EntityReferenceLink are allowed. - /// - /// The ODataResource or ODataResourceSet to write, or null for ODataEntityReferenceLink. - private void CheckForNestedResourceInfoWithContent(ODataPayloadKind contentPayloadKind, ODataItem contentPayload) - { - Debug.Assert( - contentPayloadKind == ODataPayloadKind.ResourceSet || contentPayloadKind == ODataPayloadKind.Resource || contentPayloadKind == ODataPayloadKind.EntityReferenceLink, - "Only ResourceSet, Resource or EntityReferenceLink can be specified as a payload kind for a nested resource info content."); - - Scope currentScope = this.CurrentScope; - if (currentScope.State == WriterState.NestedResourceInfo || currentScope.State == WriterState.NestedResourceInfoWithContent) - { - ODataNestedResourceInfo currentNestedResourceInfo = (ODataNestedResourceInfo)currentScope.Item; - this.InterceptException( - (thisParam, currentNestedResourceInfoParam, contentPayloadKindParam) => - { - if (thisParam.ParentResourceType != null) - { - IEdmStructuralProperty structuralProperty = thisParam.ParentResourceType.FindProperty(currentNestedResourceInfoParam.Name) as IEdmStructuralProperty; - if (structuralProperty != null) - { - thisParam.CurrentScope.ItemType = structuralProperty.Type.Definition.AsElementType(); - IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource; - - thisParam.CurrentScope.NavigationSource = parentNavigationSource; - } - else - { - IEdmNavigationProperty navigationProperty = - thisParam.WriterValidator.ValidateNestedResourceInfo(currentNestedResourceInfoParam, thisParam.ParentResourceType, contentPayloadKindParam); - if (navigationProperty != null) - { - thisParam.CurrentScope.ResourceType = navigationProperty.ToEntityType(); - IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource; - - if (thisParam.CurrentScope.NavigationSource == null) - { - IEdmPathExpression bindingPath; - thisParam.CurrentScope.NavigationSource = parentNavigationSource == null ? - null : - parentNavigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, thisParam.CurrentScope.ODataUri.Path.Segments, out bindingPath); - } - } - } - } - }, currentNestedResourceInfo, contentPayloadKind); - - if (currentScope.State == WriterState.NestedResourceInfoWithContent) - { - // If we are already in the NestedResourceInfoWithContent state, it means the caller is trying to write two items - // into the nested resource info content. This is only allowed for collection navigation property in request/response. - if (currentNestedResourceInfo.IsCollection != true) - { - this.ThrowODataException(Strings.ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent, currentNestedResourceInfo); - } - - // Note that we don't invoke duplicate property checker in this case as it's not necessary. - // What happens inside the nested resource info was already validated by the condition above. - // For collection in request we allow any combination anyway. - // For everything else we only allow a single item in the content and thus we will fail above. - } - else - { - // we are writing a nested resource info with content; change the state - this.PromoteNestedResourceInfoScope(contentPayload); - - if (!this.SkipWriting) - { - this.InterceptException( - (thisParam, currentNestedResourceInfoParam) => - { - if (!(currentNestedResourceInfoParam.SerializationInfo != null && currentNestedResourceInfoParam.SerializationInfo.IsComplex) - && (thisParam.CurrentScope.ItemType == null || thisParam.CurrentScope.ItemType.IsEntityOrEntityCollectionType())) - { - thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(currentNestedResourceInfoParam); - thisParam.StartNestedResourceInfoWithContent(currentNestedResourceInfoParam); - } - }, currentNestedResourceInfo); - } - } - } - else - { - if (contentPayloadKind == ODataPayloadKind.EntityReferenceLink) - { - Scope parenScope = this.ParentNestedResourceInfoScope; - Debug.Assert(parenScope != null); - if (parenScope.State != WriterState.NestedResourceInfo && parenScope.State != WriterState.NestedResourceInfoWithContent) - { - this.ThrowODataException(Strings.ODataWriterCore_EntityReferenceLinkWithoutNavigationLink, null); - } - } - } - } - - /// - /// Verifies that the (deleted) resource has the correct type for the (delta) resource set. - /// - /// The resource to be validated. - /// The scope for the resource to be validated. - private void ValidateResourceForResourceSet(ODataResourceBase resource, ResourceBaseScope resourceScope) - { - IEdmStructuredType resourceType = GetResourceType(resource); - NestedResourceInfoScope parentNestedResourceInfoScope = this.ParentNestedResourceInfoScope; - if (parentNestedResourceInfoScope != null) - { - // Validate the consistency of resource types in the nested resourceSet/resource - this.WriterValidator.ValidateResourceInNestedResourceInfo(resourceType, parentNestedResourceInfoScope.ResourceType); - resourceScope.ResourceTypeFromMetadata = parentNestedResourceInfoScope.ResourceType; - - this.WriterValidator.ValidateDerivedTypeConstraint(resourceType, resourceScope.ResourceTypeFromMetadata, - parentNestedResourceInfoScope.DerivedTypeConstraints, "property", ((ODataNestedResourceInfo)parentNestedResourceInfoScope.Item).Name); - } - else - { - resourceScope.ResourceTypeFromMetadata = this.ParentScope.ResourceType; - if (this.CurrentResourceSetValidator != null) - { - if (this.ParentScope.State == WriterState.DeltaResourceSet - && this.currentResourceDepth <= 1 - && resourceScope.NavigationSource != null) - { - // if the (deleted) resource is in the top level of a delta resource set, it doesn't - // need to match the delta resource set, but must match the navigation source resolved for - // the current scope - if (!resourceScope.NavigationSource.EntityType().IsAssignableFrom(resourceType)) - { - throw new ODataException(Strings.ResourceSetWithoutExpectedTypeValidator_IncompatibleTypes(resourceType.FullTypeName(), resourceScope.NavigationSource.EntityType())); - } - - resourceScope.ResourceTypeFromMetadata = resourceScope.NavigationSource.EntityType(); - } - else - { - // Validate the consistency of resource types - this.CurrentResourceSetValidator.ValidateResource(resourceType); - } - } - - if (this.ParentScope.NavigationSource != null) - { - this.WriterValidator.ValidateDerivedTypeConstraint(resourceType, resourceScope.ResourceTypeFromMetadata, - this.ParentScope.DerivedTypeConstraints, "navigation source", this.ParentScope.NavigationSource.Name); - } - } - - resourceScope.ResourceType = resourceType; - - // If writing in a delta resource set, the entity must have all key properties or the id set - if (this.ParentScope.State == WriterState.DeltaResourceSet) - { - IEdmEntityType entityType = resourceType as IEdmEntityType; - if (resource.Id == null && - entityType != null && - (this.outputContext.WritingResponse || resource is ODataDeletedResource) && - !HasKeyProperties(entityType, resource.Properties)) - { - throw new ODataException(Strings.ODataWriterCore_DeltaResourceWithoutIdOrKeyProperties); - } - } - } - - /// - /// Determines whether a collection contains all key properties for a particular entity type. - /// - /// The entity type. - /// The set of properties. - /// True if the set of properties include all key properties for the entity type; otherwise false. - private static bool HasKeyProperties(IEdmEntityType entityType, IEnumerable properties) - { - Debug.Assert(entityType != null, "entityType null"); - if (properties == null) - { - return false; - } - - return entityType.Key().All(keyProp => properties.Select(p => p.Name).Contains(keyProp.Name)); - } - - /// - /// Catch any exception thrown by the action passed in; in the exception case move the writer into - /// state Error and then rethrow the exception. - /// - /// The action to execute. - /// - /// Make sure to only use anonymous functions that don't capture state from the enclosing context, - /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method. - /// - private void InterceptException(Action action) - { - try - { - action(this); - } - catch - { - if (!IsErrorState(this.State)) - { - this.EnterScope(WriterState.Error, this.CurrentScope.Item); - } - - throw; - } - } - - /// - /// Catch any exception thrown by the action passed in; in the exception case move the writer into - /// state Error and then rethrow the exception. - /// - /// The action argument type. - /// The action to execute. - /// The argument value provided to the action. - /// - /// Make sure to only use anonymous functions that don't capture state from the enclosing context, - /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method. - /// - private void InterceptException(Action action, TArg0 arg0) - { - try - { - action(this, arg0); - } - catch - { - if (!IsErrorState(this.State)) - { - this.EnterScope(WriterState.Error, this.CurrentScope.Item); - } - - throw; - } - } - - /// - /// Catch any exception thrown by the action passed in; in the exception case move the writer into - /// state Error and then rethrow the exception. - /// - /// The delegate first argument type. - /// The delegate second argument type. - /// The action to execute. - /// The argument value provided to the action. - /// The argument value provided to the action. - /// - /// Make sure to only use anonymous functions that don't capture state from the enclosing context, - /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method. - /// - private void InterceptException(Action action, TArg0 arg0, TArg1 arg1) - { - try - { - action(this, arg0, arg1); - } - catch - { - if (!IsErrorState(this.State)) - { - this.EnterScope(WriterState.Error, this.CurrentScope.Item); - } - - throw; - } - } - - /// - /// Catch any exception thrown by the action passed in; in the exception case move the writer into - /// state Error and then rethrow the exception. - /// - /// The action to execute. - /// The task. - /// - /// Make sure to only use anonymous functions that don't capture state from the enclosing context, - /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method. - /// - private async Task InterceptExceptionAsync(Func action) - { - try - { - await action(this).ConfigureAwait(false); - } - catch - { - if (!IsErrorState(this.State)) - { - this.EnterScope(WriterState.Error, this.CurrentScope.Item); - } - - throw; - } - } - - /// - /// Catch any exception thrown by the action passed in; in the exception case move the writer into - /// state Error and then rethrow the exception. - /// - /// The action argument type. - /// The action to execute. - /// The argument value provided to the action. - /// The task. - /// - /// Make sure to only use anonymous functions that don't capture state from the enclosing context, - /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method. - /// - private async Task InterceptExceptionAsync(Func action, TArg0 arg0) - { - try - { - await action(this, arg0).ConfigureAwait(false); - } - catch - { - if (!IsErrorState(this.State)) - { - this.EnterScope(WriterState.Error, this.CurrentScope.Item); - } - - throw; - } - } - - /// - /// Increments the nested resource count by one and fails if the new value exceeds the maximum nested resource depth limit. - /// - private void IncreaseResourceDepth() - { - this.currentResourceDepth++; - - if (this.currentResourceDepth > this.outputContext.MessageWriterSettings.MessageQuotas.MaxNestingDepth) - { - this.ThrowODataException(Strings.ValidationUtils_MaxDepthOfNestedEntriesExceeded(this.outputContext.MessageWriterSettings.MessageQuotas.MaxNestingDepth), null); - } - } - - /// - /// Decrements the nested resource count by one. - /// - private void DecreaseResourceDepth() - { - Debug.Assert(this.currentResourceDepth > 0, "Resource depth should never become negative."); - - this.currentResourceDepth--; - } - - - /// - /// Notifies the implementer of the interface of relevant state changes in the writer. - /// - /// The new writer state. - private void NotifyListener(WriterState newState) - { - if (this.listener != null) - { - if (IsErrorState(newState)) - { - this.listener.OnException(); - } - else if (newState == WriterState.Completed) - { - this.listener.OnCompleted(); - } - } - } - - /// - /// Enter a new writer scope; verifies that the transition from the current state into new state is valid - /// and attaches the item to the new scope. - /// - /// The writer state to transition into. - /// The item to associate with the new scope. - [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug only cast.")] - private void EnterScope(WriterState newState, ODataItem item) - { - this.InterceptException((thisParam, newStateParam) => thisParam.ValidateTransition(newStateParam), newState); - - // If the parent scope was marked for skipping content, the new child scope should be as well. - bool skipWriting = this.SkipWriting; - - Scope currentScope = this.CurrentScope; - - IEdmNavigationSource navigationSource = null; - IEdmType itemType = null; - SelectedPropertiesNode selectedProperties = currentScope.SelectedProperties; - ODataUri odataUri = currentScope.ODataUri.Clone(); - if (odataUri.Path == null) - { - odataUri.Path = new ODataPath(); - } - - IEnumerable derivedTypeConstraints = null; - - WriterState currentState = currentScope.State; - - if (newState == WriterState.Resource || newState == WriterState.ResourceSet || newState == WriterState.Primitive || newState == WriterState.DeltaResourceSet || newState == WriterState.DeletedResource) - { - // if we're in a DeltaResourceSet and writing a resource or deleted resource then the parent may not be the navigation source - ODataResourceBase resource = item as ODataResourceBase; - if (resource != null) - { - IEdmModel model = this.outputContext.Model; - if (model != null && model.IsUserModel()) - { - try - { - string typeNameFromResource = resource.TypeName; - if (!String.IsNullOrEmpty(typeNameFromResource)) - { - // try resolving type from resource TypeName - itemType = TypeNameOracle.ResolveAndValidateTypeName( - model, - typeNameFromResource, - EdmTypeKind.None, - /* expectStructuredType */ true, - this.outputContext.WriterValidator); - } - - // Try resolving navigation source from serialization info. - ODataResourceSerializationInfo serializationInfo = resource.SerializationInfo; - if (serializationInfo != null) - { - if (serializationInfo.NavigationSourceName != null) - { - ODataUriParser uriParser = new ODataUriParser(model, new Uri(serializationInfo.NavigationSourceName, UriKind.Relative), this.outputContext.Container); - odataUri = uriParser.ParseUri(); - navigationSource = odataUri.Path.NavigationSource(); - itemType = itemType ?? navigationSource.EntityType(); - } - - if (typeNameFromResource == null) - { - // Try resolving entity type from SerializationInfo - if (!string.IsNullOrEmpty(serializationInfo.ExpectedTypeName)) - { - itemType = TypeNameOracle.ResolveAndValidateTypeName( - model, - serializationInfo.ExpectedTypeName, - EdmTypeKind.None, - /* expectStructuredType */ true, - this.outputContext.WriterValidator); - } - else if (!string.IsNullOrEmpty(serializationInfo.NavigationSourceEntityTypeName)) - { - itemType = TypeNameOracle.ResolveAndValidateTypeName( - model, - serializationInfo.NavigationSourceEntityTypeName, - EdmTypeKind.Entity, - /* expectStructuredType */ true, - this.outputContext.WriterValidator); - } - } - } - } - catch (ODataException) - { - // SerializationInfo doesn't match model. - // This should be an error but, for legacy reasons, we ignore this. - } - } - } - - if (navigationSource == null) - { - derivedTypeConstraints = currentScope.DerivedTypeConstraints; - } - else - { - derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationSource); - } - - navigationSource = navigationSource ?? currentScope.NavigationSource; - itemType = itemType ?? currentScope.ItemType; - - // This is to resolve the item type for a resource set for an undeclared nested resource info. - if (itemType == null - && (currentState == WriterState.Start || currentState == WriterState.NestedResourceInfo || currentState == WriterState.NestedResourceInfoWithContent) - && (newState == WriterState.ResourceSet || newState == WriterState.DeltaResourceSet)) - { - ODataResourceSetBase resourceSet = item as ODataResourceSetBase; - if (resourceSet != null && resourceSet.TypeName != null && this.outputContext.Model.IsUserModel()) - { - IEdmCollectionType collectionType = TypeNameOracle.ResolveAndValidateTypeName( - this.outputContext.Model, - resourceSet.TypeName, - EdmTypeKind.Collection, - false, - this.outputContext.WriterValidator) as IEdmCollectionType; - - if (collectionType != null) - { - itemType = collectionType.ElementType.Definition; - } - } - } - } - - // When writing a nested resource info, check if the link is being projected. - // If we are projecting properties, but the nav. link is not projected mark it to skip its content. - if ((currentState == WriterState.Resource || currentState == WriterState.DeletedResource) && newState == WriterState.NestedResourceInfo) - { - Debug.Assert(currentScope.Item is ODataResourceBase, "If the current state is Resource the current Item must be resource as well (and not null either)."); - Debug.Assert(item is ODataNestedResourceInfo, "If the new state is NestedResourceInfo the new item must be a nested resource info as well (and not null either)."); - ODataNestedResourceInfo nestedResourceInfo = (ODataNestedResourceInfo)item; - - if (!skipWriting) - { - selectedProperties = currentScope.SelectedProperties.GetSelectedPropertiesForNavigationProperty(currentScope.ResourceType, nestedResourceInfo.Name); - - ODataPath odataPath = odataUri.Path; - IEdmStructuredType currentResourceType = currentScope.ResourceType; - - ResourceBaseScope resourceScope = currentScope as ResourceBaseScope; - TypeSegment resourceTypeCast = null; - if (resourceScope.ResourceTypeFromMetadata != currentResourceType) - { - resourceTypeCast = new TypeSegment(currentResourceType, null); - } - - IEdmStructuralProperty structuredProperty = this.WriterValidator.ValidatePropertyDefined( - nestedResourceInfo.Name, currentResourceType) - as IEdmStructuralProperty; - - // Handle primitive or complex type property. - if (structuredProperty != null) - { - odataPath = AppendEntitySetKeySegment(odataPath, false); - itemType = structuredProperty.Type == null ? null : structuredProperty.Type.Definition.AsElementType(); - navigationSource = null; - - if (resourceTypeCast != null) - { - odataPath = odataPath.AddSegment(resourceTypeCast); - } - - odataPath = odataPath.AddPropertySegment(structuredProperty); - - derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(structuredProperty); - } - else - { - IEdmNavigationProperty navigationProperty = this.WriterValidator.ValidateNestedResourceInfo(nestedResourceInfo, currentResourceType, /*payloadKind*/null); - if (navigationProperty != null) - { - derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationProperty); - - itemType = navigationProperty.ToEntityType(); - if (!nestedResourceInfo.IsCollection.HasValue) - { - nestedResourceInfo.IsCollection = navigationProperty.Type.IsEntityCollectionType(); - } - - IEdmNavigationSource currentNavigationSource = currentScope.NavigationSource; - IEdmPathExpression bindingPath; - - if (resourceTypeCast != null) - { - odataPath = odataPath.AddSegment(resourceTypeCast); - } - - navigationSource = currentNavigationSource == null - ? null - : currentNavigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, odataPath.Segments, out bindingPath); - - SelectExpandClause clause = odataUri.SelectAndExpand; - TypeSegment typeCastFromExpand = null; - if (clause != null) - { - SelectExpandClause subClause; - clause.GetSubSelectExpandClause(nestedResourceInfo.Name, out subClause, out typeCastFromExpand); - odataUri.SelectAndExpand = subClause; - } - - switch (navigationSource.NavigationSourceKind()) - { - case EdmNavigationSourceKind.ContainedEntitySet: - // Containment cannot be written alone without odata uri. - if (!odataPath.Any()) - { - throw new ODataException(Strings.ODataWriterCore_PathInODataUriMustBeSetWhenWritingContainedElement); - } - - odataPath = AppendEntitySetKeySegment(odataPath, true); - - if (odataPath != null && typeCastFromExpand != null) - { - odataPath = odataPath.AddSegment(typeCastFromExpand); - } - - Debug.Assert(navigationSource is IEdmContainedEntitySet, "If the NavigationSourceKind is ContainedEntitySet, the navigationSource must be IEdmContainedEntitySet."); - IEdmContainedEntitySet containedEntitySet = (IEdmContainedEntitySet)navigationSource; - odataPath = odataPath.AddNavigationPropertySegment(containedEntitySet.NavigationProperty, containedEntitySet); - break; - case EdmNavigationSourceKind.EntitySet: - odataPath = new ODataPath(new EntitySetSegment(navigationSource as IEdmEntitySet)); - break; - case EdmNavigationSourceKind.Singleton: - odataPath = new ODataPath(new SingletonSegment(navigationSource as IEdmSingleton)); - break; - default: - odataPath = null; - break; - } - } - } - - odataUri.Path = odataPath; - } - } - else if ((currentState == WriterState.ResourceSet || currentState == WriterState.DeltaResourceSet) && (newState == WriterState.Resource || newState == WriterState.Primitive || newState == WriterState.ResourceSet || newState == WriterState.DeletedResource)) - { - // When writing a new resource to a resourceSet, increment the count of entries on that resourceSet. - if (currentState == WriterState.ResourceSet || currentState == WriterState.DeltaResourceSet) - { - ((ResourceSetBaseScope)currentScope).ResourceCount++; - } - } - - if (navigationSource == null) - { - navigationSource = this.CurrentScope.NavigationSource ?? odataUri.Path.TargetNavigationSource(); - } - - this.PushScope(newState, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, derivedTypeConstraints); - - this.NotifyListener(newState); - } - - /// - /// Attempt to append key segment to ODataPath. - /// - /// The ODataPath to be evaluated. - /// Whether throw if fails to append key segment. - /// The new odata path. - private ODataPath AppendEntitySetKeySegment(ODataPath odataPath, bool throwIfFail) - { - ODataPath path = odataPath; - - if (EdmExtensionMethods.HasKey(this.CurrentScope.NavigationSource, this.CurrentScope.ResourceType)) - { - IEdmEntityType currentEntityType = this.CurrentScope.ResourceType as IEdmEntityType; - ODataResourceBase resource = this.CurrentScope.Item as ODataResourceBase; - Debug.Assert(resource != null, - "If the current state is Resource the current item must be an ODataResource as well (and not null either)."); - - ODataResourceSerializationInfo serializationInfo = this.GetResourceSerializationInfo(resource); - - if (!throwIfFail) - { - if (resource.NonComputedProperties != null) - { - List> keys = new List>(); - - if (serializationInfo != null) - { - foreach (ODataProperty property in resource.NonComputedProperties) - { - if (property.SerializationInfo != null && property.SerializationInfo.PropertyKind == ODataPropertyKind.Key) - { - if(!CreateKeyValuePair(keys, property, property.Value)) - { - return path; - } - } - } - } - else - { - HashSet keySet = new HashSet(); - - foreach (IEdmStructuralProperty property in currentEntityType.Key()) - { - keySet.Add(property.Name); - } - - foreach (ODataProperty property in resource.NonComputedProperties) - { - if (keySet.Contains(property.Name)) - { - if (!CreateKeyValuePair(keys, property, property.Value)) - { - return path; - } - } - } - } - - path = path.AddKeySegment(keys.ToArray(), currentEntityType, this.CurrentScope.NavigationSource); - } - } - else - { - KeyValuePair[] keys = ODataResourceMetadataContext.GetKeyProperties(resource, - this.GetResourceSerializationInfo(resource), currentEntityType); - - path = path.AddKeySegment(keys, currentEntityType, this.CurrentScope.NavigationSource); - } - } - - - return path; - } - - private static bool CreateKeyValuePair(List> keys, ODataProperty property, object propertyValue) - { - if (propertyValue == null || (propertyValue is ODataValue && !(propertyValue is ODataEnumValue))) - { - return false; - } - - keys.Add(new KeyValuePair(property.Name, propertyValue)); - - return true; - } - - /// - /// Leave the current writer scope and return to the previous scope. - /// When reaching the top-level replace the 'Started' scope with a 'Completed' scope. - /// - /// Note that this method is never called once an error has been written or a fatal exception has been thrown. - private void LeaveScope() - { - Debug.Assert(this.State != WriterState.Error, "this.State != WriterState.Error"); - - this.scopeStack.Pop(); - - // if we are back at the root replace the 'Start' state with the 'Completed' state - if (this.scopeStack.Count == 1) - { - Scope startScope = this.scopeStack.Pop(); - Debug.Assert(startScope.State == WriterState.Start, "startScope.State == WriterState.Start"); - this.PushScope(WriterState.Completed, /*item*/null, startScope.NavigationSource, startScope.ResourceType, /*skipWriting*/false, startScope.SelectedProperties, startScope.ODataUri, null); - this.InterceptException((thisParam) => thisParam.EndPayload()); - this.NotifyListener(WriterState.Completed); - } - } - - /// - /// Promotes the current nested resource info scope to a nested resource info scope with content. - /// - /// The nested content to write. May be of either ODataResource or ODataResourceSet type. - [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Second cast only in debug.")] - private void PromoteNestedResourceInfoScope(ODataItem content) - { - Debug.Assert( - this.State == WriterState.NestedResourceInfo, - "Only a NestedResourceInfo state can be promoted right now. If this changes please review the scope replacement code below."); - Debug.Assert( - this.CurrentScope.Item != null && this.CurrentScope.Item is ODataNestedResourceInfo, - "Item must be a non-null nested resource info."); - Debug.Assert(content == null || content is ODataResourceBase || content is ODataResourceSetBase); - - this.ValidateTransition(WriterState.NestedResourceInfoWithContent); - NestedResourceInfoScope previousScope = (NestedResourceInfoScope)this.scopeStack.Pop(); - NestedResourceInfoScope newScope = previousScope.Clone(WriterState.NestedResourceInfoWithContent); - - this.scopeStack.Push(newScope); - if (newScope.ItemType == null && content != null && !SkipWriting && !(content is ODataPrimitiveValue)) - { - ODataPrimitiveValue primitiveValue = content as ODataPrimitiveValue; - if (primitiveValue != null) - { - newScope.ItemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primitiveValue.GetType()).Definition; - } - else - { - ODataResourceBase resource = content as ODataResourceBase; - newScope.ResourceType = resource != null - ? GetResourceType(resource) - : GetResourceSetType(content as ODataResourceSetBase); - } - } - } - - /// - /// Verify that the transition from the current state into new state is valid . - /// - /// The new writer state to transition into. - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "All the transition checks are encapsulated in this method.")] - private void ValidateTransition(WriterState newState) - { - if (!IsErrorState(this.State) && IsErrorState(newState)) - { - // we can always transition into an error state if we are not already in an error state - return; - } - - switch (this.State) - { - case WriterState.Start: - if (newState != WriterState.ResourceSet && newState != WriterState.Resource && newState != WriterState.DeltaResourceSet && newState != WriterState.DeletedResource) - { - throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromStart(this.State.ToString(), newState.ToString())); - } - - if ((newState == WriterState.ResourceSet || newState == WriterState.DeltaResourceSet) && !this.writingResourceSet) - { - throw new ODataException(Strings.ODataWriterCore_CannotWriteTopLevelResourceSetWithResourceWriter); - } - - if (newState == WriterState.Resource && this.writingResourceSet) - { - throw new ODataException(Strings.ODataWriterCore_CannotWriteTopLevelResourceWithResourceSetWriter); - } - - break; - case WriterState.DeletedResource: - case WriterState.Resource: - { - if (this.CurrentScope.Item == null) - { - throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromNullResource(this.State.ToString(), newState.ToString())); - } - - if (newState != WriterState.NestedResourceInfo && newState != WriterState.Property) - { - throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResource(this.State.ToString(), newState.ToString())); - } - - // TODO: The conditional expressions in the 2 `if` blocks below are adequately covered by the `if` block above? - if (newState == WriterState.DeletedResource && this.ParentScope.State != WriterState.DeltaResourceSet) - { - throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString())); - } - - if (this.State == WriterState.DeletedResource && this.Version < ODataVersion.V401 && newState == WriterState.NestedResourceInfo) - { - throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFrom40DeletedResource(this.State.ToString(), newState.ToString())); - } - } - - break; - case WriterState.ResourceSet: - // Within a typed resource set we can only write a resource. - // Within an untyped resource set we can also write a primitive value or nested resource set. - if (newState != WriterState.Resource && - (this.CurrentScope.ResourceType != null && - (this.CurrentScope.ResourceType.TypeKind != EdmTypeKind.Untyped || - (newState != WriterState.Primitive && newState != WriterState.Stream && newState != WriterState.String && newState != WriterState.ResourceSet)))) - { - throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString())); - } - - break; - case WriterState.DeltaResourceSet: - if (newState != WriterState.Resource && - newState != WriterState.DeletedResource && - !(this.ScopeLevel < 3 && (newState == WriterState.DeltaDeletedLink || newState == WriterState.DeltaLink))) - { - throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString())); - } - - break; - case WriterState.NestedResourceInfo: - if (newState != WriterState.NestedResourceInfoWithContent) - { - throw new ODataException(Strings.ODataWriterCore_InvalidStateTransition(this.State.ToString(), newState.ToString())); - } - - break; - case WriterState.NestedResourceInfoWithContent: - if (newState != WriterState.ResourceSet && newState != WriterState.Resource && newState != WriterState.Primitive && (this.Version < ODataVersion.V401 || (newState != WriterState.DeltaResourceSet && newState != WriterState.DeletedResource))) - { - throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromExpandedLink(this.State.ToString(), newState.ToString())); - } - - break; - case WriterState.Property: - PropertyInfoScope propertyScope = this.CurrentScope as PropertyInfoScope; - Debug.Assert(propertyScope != null, "Scope in WriterState.Property is not PropertyInfoScope"); - if (propertyScope.ValueWritten) - { - // we've already written the value for this property - ODataPropertyInfo propertyInfo = propertyScope.Item as ODataPropertyInfo; - Debug.Assert(propertyInfo != null, "Item in PropertyInfoScope is not ODataPropertyInfo"); - throw new ODataException(Strings.ODataWriterCore_PropertyValueAlreadyWritten(propertyInfo.Name)); - } - - if (newState == WriterState.Stream || newState == WriterState.String || newState == WriterState.Primitive) - { - propertyScope.ValueWritten = true; - } - else - { - throw new ODataException(Strings.ODataWriterCore_InvalidStateTransition(this.State.ToString(), newState.ToString())); - } - - break; - case WriterState.Stream: - case WriterState.String: - throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed); - case WriterState.Completed: - // we should never see a state transition when in state 'Completed' - throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), newState.ToString())); - case WriterState.Error: - if (newState != WriterState.Error) - { - // No more state transitions once we are in error state except for the fatal error - throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromError(this.State.ToString(), newState.ToString())); - } - - break; - default: - throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_ValidateTransition_UnreachableCodePath)); - } - } - - /// - /// Create a new writer scope. - /// - /// The writer state of the scope to create. - /// The item attached to the scope to create. - /// The navigation source we are going to write resource set for. - /// The structured type for the items in the resource set to be written (or null if the navigationSource base type should be used). - /// true if the content of the scope to create should not be written. - /// The selected properties of this scope. - /// The OdataUri info of this scope. - /// The derived type constraints. - [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug.Assert check only.")] - private void PushScope(WriterState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, - IEnumerable derivedTypeConstraints) - { - IEdmStructuredType resourceType = itemType as IEdmStructuredType; - - Debug.Assert( - state == WriterState.Error || - state == WriterState.Resource && (item == null || item is ODataResource) || - state == WriterState.DeletedResource && (item == null || item is ODataDeletedResource) || - state == WriterState.DeltaLink && (item == null || item is ODataDeltaLink) || - state == WriterState.DeltaDeletedLink && (item == null || item is ODataDeltaDeletedLink) || - state == WriterState.ResourceSet && item is ODataResourceSet || - state == WriterState.DeltaResourceSet && item is ODataDeltaResourceSet || - state == WriterState.Primitive && (item == null || item is ODataPrimitiveValue) || - state == WriterState.Property && (item is ODataPropertyInfo) || - state == WriterState.NestedResourceInfo && item is ODataNestedResourceInfo || - state == WriterState.NestedResourceInfoWithContent && item is ODataNestedResourceInfo || - state == WriterState.Stream && item == null || - state == WriterState.String && item == null || - state == WriterState.Start && item == null || - state == WriterState.Completed && item == null, - "Writer state and associated item do not match."); - - bool isUndeclaredResourceOrResourceSet = false; - if ((state == WriterState.Resource || state == WriterState.ResourceSet) - && (this.CurrentScope.State == WriterState.NestedResourceInfo || this.CurrentScope.State == WriterState.NestedResourceInfoWithContent)) - { - isUndeclaredResourceOrResourceSet = this.IsUndeclared(this.CurrentScope.Item as ODataNestedResourceInfo); - } - - Scope scope; - switch (state) - { - case WriterState.Resource: - scope = this.CreateResourceScope((ODataResource)item, navigationSource, resourceType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet); - break; - case WriterState.DeletedResource: - scope = this.CreateDeletedResourceScope((ODataDeletedResource)item, navigationSource, (IEdmEntityType)itemType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet); - break; - case WriterState.DeltaLink: - case WriterState.DeltaDeletedLink: - scope = this.CreateDeltaLinkScope((ODataDeltaLinkBase)item, navigationSource, (IEdmEntityType)itemType, selectedProperties, odataUri); - break; - case WriterState.ResourceSet: - scope = this.CreateResourceSetScope((ODataResourceSet)item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet); - if (this.outputContext.Model.IsUserModel()) - { - Debug.Assert(scope is ResourceSetBaseScope, "Create a scope for a resource set that is not a ResourceSetBaseScope"); - ((ResourceSetBaseScope)scope).ResourceTypeValidator = new ResourceSetWithoutExpectedTypeValidator(itemType); - } - - break; - case WriterState.DeltaResourceSet: - scope = this.CreateDeltaResourceSetScope((ODataDeltaResourceSet)item, navigationSource, resourceType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet); - if (this.outputContext.Model.IsUserModel()) - { - Debug.Assert(scope is ResourceSetBaseScope, "Create a scope for a delta resource set that is not a ResourceSetBaseScope"); - ((ResourceSetBaseScope)scope).ResourceTypeValidator = new ResourceSetWithoutExpectedTypeValidator(resourceType); - } - - break; - case WriterState.Property: - scope = this.CreatePropertyInfoScope((ODataPropertyInfo)item, navigationSource, resourceType, selectedProperties, odataUri); - break; - case WriterState.NestedResourceInfo: // fall through - case WriterState.NestedResourceInfoWithContent: - scope = this.CreateNestedResourceInfoScope(state, (ODataNestedResourceInfo)item, navigationSource, itemType, skipWriting, selectedProperties, odataUri); - break; - case WriterState.Start: - scope = new Scope(state, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, /*enableDelta*/ true); - break; - case WriterState.Primitive: // fall through - case WriterState.Stream: // fall through - case WriterState.String: // fall through - case WriterState.Completed: // fall through - case WriterState.Error: - scope = new Scope(state, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, /*enableDelta*/ false); - break; - default: - string errorMessage = Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_Scope_Create_UnreachableCodePath); - Debug.Assert(false, errorMessage); - throw new ODataException(errorMessage); - } - - scope.DerivedTypeConstraints = derivedTypeConstraints?.ToList(); - this.scopeStack.Push(scope); - } - - /// - /// Test to see if for a complex property or a collection of complex property, or a navigation property is declared or not. - /// - /// The nested info in question - /// true if the nested info is undeclared; false if it is not, or if it cannot be determined - private bool IsUndeclared(ODataNestedResourceInfo nestedResourceInfo) - { - Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); - - if (nestedResourceInfo.SerializationInfo != null) - { - return nestedResourceInfo.SerializationInfo.IsUndeclared; - } - else - { - return this.ParentResourceType != null && (this.ParentResourceType.FindProperty((this.CurrentScope.Item as ODataNestedResourceInfo).Name) == null); - } - } - - /// - /// Asynchronously verifies that calling is valid. - /// - /// true if the call is to be synchronous; false otherwise. - /// The resource set/collection to write. - /// A task that represents the asynchronous operation. - private async Task VerifyCanWriteStartResourceSetAsync(bool synchronousCall, ODataResourceSet resourceSet) - { - ExceptionUtils.CheckArgumentNotNull(resourceSet, "resourceSet"); - - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - - await this.StartPayloadInStartStateAsync() - .ConfigureAwait(false); - } - - /// - /// Asynchronously start writing a resource set - implementation of the actual functionality. - /// - /// The resource set to write. - /// A task that represents the asynchronous write operation. - private async Task WriteStartResourceSetImplementationAsync(ODataResourceSet resourceSet) - { - await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.ResourceSet, resourceSet) - .ConfigureAwait(false); - this.EnterScope(WriterState.ResourceSet, resourceSet); - - if (!this.SkipWriting) - { - await this.InterceptExceptionAsync( - async (thisParam, resourceSetParam) => - { - // Verify query count - if (resourceSetParam.Count.HasValue) - { - // Check that Count is not set for requests - if (!thisParam.outputContext.WritingResponse) - { - thisParam.ThrowODataException(Strings.ODataWriterCore_QueryCountInRequest, resourceSetParam); - } - } - - await thisParam.StartResourceSetAsync(resourceSetParam) - .ConfigureAwait(false); - }, resourceSet).ConfigureAwait(false); - } - } - - /// - /// Asynchronously verifies that calling is valid. - /// - /// true if the call is to be synchronous; false otherwise. - /// The delta resource set/collection to write. - /// A task that represents the asynchronous operation. - private async Task VerifyCanWriteStartDeltaResourceSetAsync(bool synchronousCall, ODataDeltaResourceSet deltaResourceSet) - { - ExceptionUtils.CheckArgumentNotNull(deltaResourceSet, "deltaResourceSet"); - this.VerifyNotDisposed(); - this.VerifyCallAllowed(synchronousCall); - - await this.StartPayloadInStartStateAsync() - .ConfigureAwait(false); - } - - /// - /// Asynchronously start writing a delta resource set - implementation of the actual functionality. - /// - /// The delta resource set to write. - /// A task that represents the asynchronous write operation. - private async Task WriteStartDeltaResourceSetImplementationAsync(ODataDeltaResourceSet deltaResourceSet) - { - await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.ResourceSet, deltaResourceSet) - .ConfigureAwait(false); - this.EnterScope(WriterState.DeltaResourceSet, deltaResourceSet); - - await this.InterceptExceptionAsync( - async (thisParam, deltaResourceSetParam) => - { - if (!thisParam.outputContext.WritingResponse) - { - // Check that links are not set for requests - if (deltaResourceSetParam.NextPageLink != null) - { - thisParam.ThrowODataException(Strings.ODataWriterCore_QueryNextLinkInRequest, deltaResourceSetParam); - } - - if (deltaResourceSetParam.DeltaLink != null) - { - thisParam.ThrowODataException(Strings.ODataWriterCore_QueryDeltaLinkInRequest, deltaResourceSetParam); - } - } - - await thisParam.StartDeltaResourceSetAsync(deltaResourceSetParam) - .ConfigureAwait(false); - }, deltaResourceSet).ConfigureAwait(false); - } - - /// - /// Asynchronously start writing a resource - implementation of the actual functionality. - /// - /// Resource/item to write. - /// A task that represents the asynchronous write operation. - private async Task WriteStartResourceImplementationAsync(ODataResource resource) - { - await this.StartPayloadInStartStateAsync() - .ConfigureAwait(false); - await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.Resource, resource) - .ConfigureAwait(false); - this.EnterScope(WriterState.Resource, resource); - - if (!this.SkipWriting) - { - this.IncreaseResourceDepth(); - await this.InterceptExceptionAsync( - async (thisParam, resourceParam) => - { - if (resourceParam != null) - { - ResourceScope resourceScope = (ResourceScope)thisParam.CurrentScope; - thisParam.ValidateResourceForResourceSet(resourceParam, resourceScope); - await thisParam.PrepareResourceForWriteStartAsync( - resourceScope, - resourceParam, - thisParam.outputContext.WritingResponse, - resourceScope.SelectedProperties).ConfigureAwait(false); - } - - await thisParam.StartResourceAsync(resourceParam) - .ConfigureAwait(false); - }, resource).ConfigureAwait(false); - } - } - - /// - /// Asynchronously start writing a deleted resource - implementation of the actual functionality. - /// - /// Resource/item to write. - /// A task that represents the asynchronous write operation. - private async Task WriteStartDeletedResourceImplementationAsync(ODataDeletedResource resource) - { - Debug.Assert(resource != null, "resource != null"); - - await this.StartPayloadInStartStateAsync() - .ConfigureAwait(false); - await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.Resource, resource) - .ConfigureAwait(false); - this.EnterScope(WriterState.DeletedResource, resource); - this.IncreaseResourceDepth(); - - await this.InterceptExceptionAsync( - async (thisParam, resourceParam) => - { - DeletedResourceScope resourceScope = thisParam.CurrentScope as DeletedResourceScope; - this.ValidateResourceForResourceSet(resourceParam, resourceScope); - - await this.PrepareDeletedResourceForWriteStartAsync( - resourceScope, - resourceParam, - thisParam.outputContext.WritingResponse, - resourceScope.SelectedProperties).ConfigureAwait(false); - await thisParam.StartDeletedResourceAsync(resource) - .ConfigureAwait(false); - }, resource).ConfigureAwait(false); - } - - /// - /// Asynchronously start writing a property - implementation of the actual functionality. - /// - /// Property to write. - /// A task that represents the asynchronous write operation. - private async Task WriteStartPropertyImplementationAsync(ODataPropertyInfo property) - { - this.EnterScope(WriterState.Property, property); - - if (!this.SkipWriting) - { - await this.InterceptExceptionAsync( - async (thisParam, propertyParam) => - { - await thisParam.StartPropertyAsync(propertyParam) - .ConfigureAwait(false); - - if (propertyParam is ODataProperty) - { - PropertyInfoScope scope = thisParam.CurrentScope as PropertyInfoScope; - Debug.Assert(scope != null, "Scope for ODataPropertyInfo is not ODataPropertyInfoScope"); - scope.ValueWritten = true; - } - }, property).ConfigureAwait(false); - } - } - - /// - /// Asynchronously create a write stream - implementation of the actual functionality. - /// - /// A task that represents the asynchronous operation. - /// The value of the TResult parameter contains the stream for writing the binary. - private async Task CreateWriteStreamImplementationAsync() - { - this.EnterScope(WriterState.Stream, null); - Stream underlyingStream = await this.StartBinaryStreamAsync() - .ConfigureAwait(false); - - return new ODataNotificationStream(underlyingStream, /*listener*/ this, /*synchronous*/ false); - } - - /// - /// Asynchronously create a text writer - implementation of the actual functionality. - /// - /// A task that represents the asynchronous operation. - /// The value of the TResult parameter contains the textwriter for writing a string value. - private async Task CreateTextWriterImplementationAsync() - { - this.EnterScope(WriterState.String, null); - TextWriter textWriter = await this.StartTextWriterAsync() - .ConfigureAwait(false); - - return new ODataNotificationWriter(textWriter, /*listener*/ this, /*synchronous*/ false); - } - - /// - /// Asynchronously finish writing a resource set/resource/nested resource info. - /// - /// A task that represents the asynchronous write operation. - private Task WriteEndImplementationAsync() - { - return this.InterceptExceptionAsync( - async (thisParam) => - { - bool wasenableDelta; - Scope currentScope = thisParam.CurrentScope; - - switch (currentScope.State) - { - case WriterState.Resource: - if (!thisParam.SkipWriting) - { - ODataResource resource = (ODataResource)currentScope.Item; - - await thisParam.EndResourceAsync(resource) - .ConfigureAwait(false); - thisParam.DecreaseResourceDepth(); - } - - break; - case WriterState.DeletedResource: - if (!thisParam.SkipWriting) - { - ODataDeletedResource resource = (ODataDeletedResource)currentScope.Item; - - await thisParam.EndDeletedResourceAsync(resource) - .ConfigureAwait(false); - thisParam.DecreaseResourceDepth(); - } - - break; - case WriterState.ResourceSet: - if (!thisParam.SkipWriting) - { - ODataResourceSet resourceSet = (ODataResourceSet)currentScope.Item; - WriterValidationUtils.ValidateResourceSetAtEnd(resourceSet, !thisParam.outputContext.WritingResponse); - await thisParam.EndResourceSetAsync(resourceSet) - .ConfigureAwait(false); - } - - break; - case WriterState.DeltaLink: - case WriterState.DeltaDeletedLink: - break; - case WriterState.DeltaResourceSet: - if (!thisParam.SkipWriting) - { - ODataDeltaResourceSet deltaResourceSet = (ODataDeltaResourceSet)currentScope.Item; - WriterValidationUtils.ValidateDeltaResourceSetAtEnd(deltaResourceSet, !thisParam.outputContext.WritingResponse); - await thisParam.EndDeltaResourceSetAsync(deltaResourceSet) - .ConfigureAwait(false); - } - - break; - case WriterState.NestedResourceInfo: - if (!thisParam.outputContext.WritingResponse) - { - throw new ODataException(Strings.ODataWriterCore_DeferredLinkInRequest); - } - - if (!thisParam.SkipWriting) - { - ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item; - thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(link); - await thisParam.WriteDeferredNestedResourceInfoAsync(link) - .ConfigureAwait(false); - - thisParam.MarkNestedResourceInfoAsProcessed(link); - } - - break; - case WriterState.NestedResourceInfoWithContent: - if (!thisParam.SkipWriting) - { - ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item; - await thisParam.EndNestedResourceInfoWithContentAsync(link) - .ConfigureAwait(false); - - thisParam.MarkNestedResourceInfoAsProcessed(link); - } - - break; - case WriterState.Property: - { - ODataPropertyInfo property = (ODataPropertyInfo)currentScope.Item; - await thisParam.EndPropertyAsync(property) - .ConfigureAwait(false); - } - - break; - case WriterState.Primitive: - // WriteEnd for WriterState.Primitive is a no-op; just leave scope - break; - case WriterState.Stream: - case WriterState.String: - throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed); - case WriterState.Start: // fall through - case WriterState.Completed: // fall through - case WriterState.Error: // fall through - throw new ODataException(Strings.ODataWriterCore_WriteEndCalledInInvalidState(currentScope.State.ToString())); - default: - throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_WriteEnd_UnreachableCodePath)); - } - - await thisParam.LeaveScopeAsync() - .ConfigureAwait(false); - }); - } - - /// - /// Asynchronously write an entity reference link. - /// - /// The entity reference link to write. - /// A task that represents the asynchronous write operation. - private async Task WriteEntityReferenceLinkImplementationAsync(ODataEntityReferenceLink entityReferenceLink) - { - Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null"); - - await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.EntityReferenceLink, null) - .ConfigureAwait(false); - Debug.Assert( - this.CurrentScope.Item is ODataNestedResourceInfo || this.ParentNestedResourceInfoScope.Item is ODataNestedResourceInfo, - "The CheckForNestedResourceInfoWithContent should have verified that entity reference link can only be written inside a nested resource info."); - - if (!this.SkipWriting) - { - await this.InterceptExceptionAsync( - async (thisParam, entityReferenceLinkParam) => - { - WriterValidationUtils.ValidateEntityReferenceLink(entityReferenceLinkParam); - - ODataNestedResourceInfo nestedInfo = thisParam.CurrentScope.Item as ODataNestedResourceInfo; - if (nestedInfo == null) - { - NestedResourceInfoScope nestedResourceInfoScope = thisParam.ParentNestedResourceInfoScope; - Debug.Assert(nestedResourceInfoScope != null); - nestedInfo = (ODataNestedResourceInfo)nestedResourceInfoScope.Item; - } - - await thisParam.WriteEntityReferenceInNavigationLinkContentAsync(nestedInfo, entityReferenceLinkParam) - .ConfigureAwait(false); - }, entityReferenceLink).ConfigureAwait(false); - } - } - - /// - /// Asynchronously checks whether we are currently writing the first top-level element; if so call StartPayloadAsync - /// - /// A task that represents the asynchronous write operation. - private Task StartPayloadInStartStateAsync() - { - if (this.State == WriterState.Start) - { - return this.InterceptExceptionAsync((thisParam) => thisParam.StartPayloadAsync()); - } - - return TaskUtils.CompletedTask; - } - - /// - /// Asynchronously checks whether we are currently writing a nested resource info and switches to NestedResourceInfoWithContent state if we do. - /// - /// - /// What kind of payload kind is being written as the content of a nested resource info. - /// Only Resource Set, Resource or EntityReferenceLink are allowed. - /// - /// The ODataResource or ODataResourceSet to write, or null for ODataEntityReferenceLink. - /// A task that represents the asynchronous operation. - private async Task CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind contentPayloadKind, ODataItem contentPayload) - { - Debug.Assert( - contentPayloadKind == ODataPayloadKind.ResourceSet || contentPayloadKind == ODataPayloadKind.Resource || contentPayloadKind == ODataPayloadKind.EntityReferenceLink, - "Only ResourceSet, Resource or EntityReferenceLink can be specified as a payload kind for a nested resource info content."); - - Scope currentScope = this.CurrentScope; - if (currentScope.State == WriterState.NestedResourceInfo || currentScope.State == WriterState.NestedResourceInfoWithContent) - { - ODataNestedResourceInfo currentNestedResourceInfo = (ODataNestedResourceInfo)currentScope.Item; - - this.InterceptException( - (thisParam, currentNestedResourceInfoParam, contentPayloadKindParam) => - { - if (thisParam.ParentResourceType != null) - { - IEdmStructuralProperty structuralProperty = thisParam.ParentResourceType.FindProperty( - currentNestedResourceInfoParam.Name) as IEdmStructuralProperty; - if (structuralProperty != null) - { - thisParam.CurrentScope.ItemType = structuralProperty.Type.Definition.AsElementType(); - IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource; - - thisParam.CurrentScope.NavigationSource = parentNavigationSource; - } - else - { - IEdmNavigationProperty navigationProperty = thisParam.WriterValidator.ValidateNestedResourceInfo( - currentNestedResourceInfoParam, - thisParam.ParentResourceType, - contentPayloadKindParam); - if (navigationProperty != null) - { - thisParam.CurrentScope.ResourceType = navigationProperty.ToEntityType(); - IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource; - - if (thisParam.CurrentScope.NavigationSource == null) - { - IEdmPathExpression bindingPath; - thisParam.CurrentScope.NavigationSource = parentNavigationSource?.FindNavigationTarget( - navigationProperty, - BindingPathHelper.MatchBindingPath, - thisParam.CurrentScope.ODataUri.Path.Segments, - out bindingPath); - } - } - } - } - }, currentNestedResourceInfo, contentPayloadKind); - - if (currentScope.State == WriterState.NestedResourceInfoWithContent) - { - // If we are already in the NestedResourceInfoWithContent state, it means the caller is trying to write two items - // into the nested resource info content. This is only allowed for collection navigation property in request/response. - if (currentNestedResourceInfo.IsCollection != true) - { - this.ThrowODataException(Strings.ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent, currentNestedResourceInfo); - } - - // Note that we don't invoke duplicate property checker in this case as it's not necessary. - // What happens inside the nested resource info was already validated by the condition above. - // For collection in request we allow any combination anyway. - // For everything else we only allow a single item in the content and thus we will fail above. - } - else - { - // We are writing a nested resource info with content; change the state - this.PromoteNestedResourceInfoScope(contentPayload); - - if (!this.SkipWriting) - { - await this.InterceptExceptionAsync( - async (thisParam, currentNestedResourceInfoParam) => - { - if (!(currentNestedResourceInfoParam.SerializationInfo != null && currentNestedResourceInfoParam.SerializationInfo.IsComplex) - && (thisParam.CurrentScope.ItemType == null || thisParam.CurrentScope.ItemType.IsEntityOrEntityCollectionType())) - { - thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(currentNestedResourceInfoParam); - await thisParam.StartNestedResourceInfoWithContentAsync(currentNestedResourceInfoParam) - .ConfigureAwait(false); - } - }, currentNestedResourceInfo).ConfigureAwait(false); - } - } - } - else - { - if (contentPayloadKind == ODataPayloadKind.EntityReferenceLink) - { - Scope parentScope = this.ParentNestedResourceInfoScope; - Debug.Assert(parentScope != null); - if (parentScope.State != WriterState.NestedResourceInfo && parentScope.State != WriterState.NestedResourceInfoWithContent) - { - this.ThrowODataException(Strings.ODataWriterCore_EntityReferenceLinkWithoutNavigationLink, null); - } - } - } - } - - /// - /// Asynchronously leave the current writer scope and return to the previous scope. - /// When reaching the top-level replace the 'Started' scope with a 'Completed' scope. - /// - /// Note that this method is never called once an error has been written or a fatal exception has been thrown. - private async Task LeaveScopeAsync() - { - Debug.Assert(this.State != WriterState.Error, "this.State != WriterState.Error"); - - this.scopeStack.Pop(); - - // if we are back at the root replace the 'Start' state with the 'Completed' state - if (this.scopeStack.Count == 1) - { - Scope startScope = this.scopeStack.Pop(); - Debug.Assert(startScope.State == WriterState.Start, "startScope.State == WriterState.Start"); - this.PushScope( - WriterState.Completed, - /*item*/ null, - startScope.NavigationSource, - startScope.ResourceType, - /*skipWriting*/ false, - startScope.SelectedProperties, - startScope.ODataUri, - /*derivedTypeConstraints*/ null); - await this.InterceptExceptionAsync((thisParam) => thisParam.EndPayloadAsync()) - .ConfigureAwait(false); - this.NotifyListener(WriterState.Completed); - } - } - - /// - /// Lightweight wrapper for the stack of scopes which exposes a few helper properties for getting parent scopes. - /// - internal sealed class ScopeStack - { - /// - /// Use a list to store the scopes instead of a true stack so that parent/grandparent lookups will be fast. - /// - private readonly List scopes = new List(); - - /// - /// Initializes a new instance of the class. - /// - internal ScopeStack() - { - } - - /// - /// Gets the count of items in the stack. - /// - internal int Count - { - get - { - return this.scopes.Count; - } - } - - /// - /// Gets the scope below the current scope on top of the stack. - /// - internal Scope Parent - { - get - { - Debug.Assert(this.scopes.Count > 1, "this.scopes.Count > 1"); - return this.scopes[this.scopes.Count - 2]; - } - } - - /// - /// Gets the scope below the parent of the current scope on top of the stack. - /// - internal Scope ParentOfParent - { - get - { - Debug.Assert(this.scopes.Count > 2, "this.scopes.Count > 2"); - return this.scopes[this.scopes.Count - 3]; - } - } - - /// - /// Gets the scope below the current scope on top of the stack or null if there is only one item on the stack or the stack is empty. - /// - internal Scope ParentOrNull - { - get - { - return this.Count == 0 ? null : this.Parent; - } - } - - /// - /// Pushes the specified scope onto the stack. - /// - /// The scope. - internal void Push(Scope scope) - { - Debug.Assert(scope != null, "scope != null"); - this.scopes.Add(scope); - } - - /// - /// Pops the current scope off the stack. - /// - /// The popped scope. - internal Scope Pop() - { - Debug.Assert(this.scopes.Count > 0, "this.scopes.Count > 0"); - int last = this.scopes.Count - 1; - Scope current = this.scopes[last]; - this.scopes.RemoveAt(last); - return current; - } - - /// - /// Peeks at the current scope on the top of the stack. - /// - /// The current scope at the top of the stack. - internal Scope Peek() - { - Debug.Assert(this.scopes.Count > 0, "this.scopes.Count > 0"); - return this.scopes[this.scopes.Count - 1]; - } - } - - /// - /// A writer scope; keeping track of the current writer state and an item associated with this state. - /// - internal class Scope - { - /// The writer state of this scope. - private readonly WriterState state; - - /// The item attached to this scope. - private readonly ODataItem item; - - /// Set to true if the content of the scope should not be written. - /// This is used when writing navigation links which were not projected on the owning resource. - private readonly bool skipWriting; - - /// The selected properties for the current scope. - private readonly SelectedPropertiesNode selectedProperties; - - /// The navigation source we are going to write entities for. - private IEdmNavigationSource navigationSource; - - /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used). - private IEdmStructuredType resourceType; - - /// The IEdmType of the item (may not be structured for primitive types). - private IEdmType itemType; - - /// The odata uri info for current scope. - private ODataUri odataUri; - - /// Whether we are in the context of writing a delta collection. - private bool enableDelta; - - /// - /// Constructor creating a new writer scope. - /// - /// The writer state of this scope. - /// The item attached to this scope. - /// The navigation source we are going to write resource set for. - /// The type for the items in the resource set to be written (or null if the entity set base type should be used). - /// true if the content of this scope should not be written. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - /// Whether we are in the context of writing a delta collection. - internal Scope(WriterState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool enableDelta) - { - this.state = state; - this.item = item; - this.itemType = itemType; - this.resourceType = itemType as IEdmStructuredType; - this.navigationSource = navigationSource; - this.skipWriting = skipWriting; - this.selectedProperties = selectedProperties; - this.odataUri = odataUri; - this.enableDelta = enableDelta; - } - - /// - /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). - /// - public IEdmStructuredType ResourceType - { - get - { - return this.resourceType; - } - - set - { - this.resourceType = value; - this.itemType = value; - } - } - - /// - /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). - /// - public IEdmType ItemType - { - get - { - return this.itemType; - } - - set - { - this.itemType = value; - this.resourceType = value as IEdmStructuredType; - } - } - - /// - /// The writer state of this scope. - /// - internal WriterState State - { - get - { - return this.state; - } - } - - /// - /// The item attached to this scope. - /// - internal ODataItem Item - { - get - { - return this.item; - } - } - - /// The navigation source we are going to write entities for. - internal IEdmNavigationSource NavigationSource - { - get - { - return this.navigationSource; - } - - set - { - this.navigationSource = value; - } - } - - /// The selected properties for the current scope. - internal SelectedPropertiesNode SelectedProperties - { - get - { - return this.selectedProperties; - } - } - - /// The odata Uri for the current scope. - internal ODataUri ODataUri - { - get - { - Debug.Assert(this.odataUri != null, "this.odataUri != null"); - return this.odataUri; - } - } - - /// - /// Set to true if the content of this scope should not be written. - /// - internal bool SkipWriting - { - get - { - return this.skipWriting; - } - } - - /// - /// True if we are in the process of writing a delta collection. - /// - public bool EnableDelta - { - get - { - return this.enableDelta; - } - - protected set - { - this.enableDelta = value; - } - } - - /// Gets or sets the derived type constraints for the current scope. - internal List DerivedTypeConstraints { get; set; } - } - - /// - /// A base scope for a resourceSet. - /// - internal abstract class ResourceSetBaseScope : Scope - { - /// The serialization info for the current resourceSet. - private readonly ODataResourceSerializationInfo serializationInfo; - - /// - /// The to use for entries in this resourceSet. - /// - private ResourceSetWithoutExpectedTypeValidator resourceTypeValidator; - - /// The number of entries in this resourceSet seen so far. - private int resourceCount; - - /// Maintains the write status for each annotation using its key. - private InstanceAnnotationWriteTracker instanceAnnotationWriteTracker; - - /// The type context to answer basic questions regarding the type info of the resource. - private ODataResourceTypeContext typeContext; - - /// - /// Constructor to create a new resource set scope. - /// - /// The writer state for the scope. - /// The resourceSet for the new scope. - /// The navigation source we are going to write resource set for. - /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). - /// true if the content of the scope to create should not be written. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - internal ResourceSetBaseScope(WriterState writerState, ODataResourceSetBase resourceSet, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri) - : base(writerState, resourceSet, navigationSource, itemType, skipWriting, selectedProperties, odataUri, writerState == WriterState.DeltaResourceSet) - { - this.serializationInfo = resourceSet.SerializationInfo; - } - - /// - /// The number of entries in this resource Set seen so far. - /// - internal int ResourceCount - { - get - { - return this.resourceCount; - } - - set - { - this.resourceCount = value; - } - } - - /// - /// Tracks the write status of the annotations. - /// - internal InstanceAnnotationWriteTracker InstanceAnnotationWriteTracker - { - get - { - if (this.instanceAnnotationWriteTracker == null) - { - this.instanceAnnotationWriteTracker = new InstanceAnnotationWriteTracker(); - } - - return this.instanceAnnotationWriteTracker; - } - } - - /// - /// Validator for resource type. - /// - internal ResourceSetWithoutExpectedTypeValidator ResourceTypeValidator - { - get - { - return this.resourceTypeValidator; - } - - set - { - this.resourceTypeValidator = value; - } - } - - /// - /// Gets or creates the type context to answer basic questions regarding the type info of the resource. - /// - /// True if writing a response payload, false otherwise. - /// The type context to answer basic questions regarding the type info of the resource. - internal ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse) - { - if (this.typeContext == null) - { - // For Entity, currently we check the navigation source. - // For Complex, we don't have navigation source, So we shouldn't check it. - // If ResourceType is not provided, serialization info or navigation source info should be provided. - bool throwIfMissingTypeInfo = writingResponse && (this.ResourceType == null || this.ResourceType.TypeKind == EdmTypeKind.Entity); - - this.typeContext = ODataResourceTypeContext.Create( - this.serializationInfo, - this.NavigationSource, - EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource), - this.ResourceType, - throwIfMissingTypeInfo); - } - - return this.typeContext; - } - } - - /// - /// A scope for a resource set. - /// - internal abstract class ResourceSetScope : ResourceSetBaseScope - { - /// - /// Constructor to create a new resource set scope. - /// - /// The resource set for the new scope. - /// The navigation source we are going to write resource set for. - /// The type of the items in the resource set to be written (or null if the entity set base type should be used). - /// true if the content of the scope to create should not be written. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - protected ResourceSetScope(ODataResourceSet item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri) - : base(WriterState.ResourceSet, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri) - { - } - } - - /// - /// A scope for a delta resource set. - /// - internal abstract class DeltaResourceSetScope : ResourceSetBaseScope - { - /// - /// Constructor to create a new resource set scope. - /// - /// The resource set for the new scope. - /// The navigation source we are going to write resource set for. - /// The structured type of the items in the resource set to be written (or null if the entity set base type should be used). - /// The selected properties of this scope. - /// The ODataUri info of this scope. - protected DeltaResourceSetScope(ODataDeltaResourceSet item, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) - : base(WriterState.DeltaResourceSet, item, navigationSource, resourceType, false /*skip writing*/, selectedProperties, odataUri) - { - } - - /// - /// The context uri info created for this scope. - /// - public ODataContextUrlInfo ContextUriInfo { get; set; } - } - - /// - /// A base scope for a resource. - /// - internal class ResourceBaseScope : Scope - { - /// Checker to detect duplicate property names. - private readonly IDuplicatePropertyNameChecker duplicatePropertyNameChecker; - - /// The serialization info for the current resource. - private readonly ODataResourceSerializationInfo serializationInfo; - - /// The resource type which was derived from the model (may be either the same as structured type or its base type. - private IEdmStructuredType resourceTypeFromMetadata; - - /// The type context to answer basic questions regarding the type info of the resource. - private ODataResourceTypeContext typeContext; - - /// Maintains the write status for each annotation using its key. - private InstanceAnnotationWriteTracker instanceAnnotationWriteTracker; - - /// - /// Constructor to create a new resource scope. - /// - /// The writer state of this scope. - /// The resource for the new scope. - /// The serialization info for the current resource. - /// The navigation source we are going to write resource set for. - /// The type for the items in the resource set to be written (or null if the entity set base type should be used). - /// true if the content of the scope to create should not be written. - /// The The settings of the writer. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - internal ResourceBaseScope(WriterState state, ODataResourceBase resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri) - : base(state, resource, navigationSource, itemType, skipWriting, selectedProperties, odataUri, /*enableDelta*/ true) - { - Debug.Assert(writerSettings != null, "writerBehavior != null"); - - if (resource != null) - { - duplicatePropertyNameChecker = writerSettings.Validator.CreateDuplicatePropertyNameChecker(); - } - - this.serializationInfo = serializationInfo; - } - - /// - /// The structured type which was derived from the model, i.e. the expected structured type, which may be either the same as structured type or its base type. - /// For example, if we are writing a resource set of Customers and the current resource is of DerivedCustomer, this.ResourceTypeFromMetadata would be Customer and this.ResourceType would be DerivedCustomer. - /// - public IEdmStructuredType ResourceTypeFromMetadata - { - get - { - return this.resourceTypeFromMetadata; - } - - internal set - { - this.resourceTypeFromMetadata = value; - } - } - - /// - /// The serialization info for the current resource. - /// - public ODataResourceSerializationInfo SerializationInfo - { - get { return this.serializationInfo; } - } - - /// - /// Checker to detect duplicate property names. - /// - internal IDuplicatePropertyNameChecker DuplicatePropertyNameChecker - { - get - { - return duplicatePropertyNameChecker; - } - } - - /// - /// Tracks the write status of the annotations. - /// - internal InstanceAnnotationWriteTracker InstanceAnnotationWriteTracker - { - get - { - if (this.instanceAnnotationWriteTracker == null) - { - this.instanceAnnotationWriteTracker = new InstanceAnnotationWriteTracker(); - } - - return this.instanceAnnotationWriteTracker; - } - } - - /// - /// Gets or creates the type context to answer basic questions regarding the type info of the resource. - /// - /// True if writing a response payload, false otherwise. - /// The type context to answer basic questions regarding the type info of the resource. - public ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse) - { - if (this.typeContext == null) - { - IEdmStructuredType expectedResourceType = this.ResourceTypeFromMetadata ?? this.ResourceType; - - // For entity, we will check the navigation source info - bool throwIfMissingTypeInfo = writingResponse && (expectedResourceType == null || expectedResourceType.TypeKind == EdmTypeKind.Entity); - - this.typeContext = ODataResourceTypeContext.Create( - this.serializationInfo, - this.NavigationSource, - EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource), - expectedResourceType, - throwIfMissingTypeInfo); - } - - return this.typeContext; - } - } - - /// - /// A base scope for a resource. - /// - internal class ResourceScope : ResourceBaseScope - { - /// - /// Constructor to create a new resource scope. - /// - /// The resource for the new scope. - /// The serialization info for the current resource. - /// The navigation source we are going to write resource set for. - /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). - /// true if the content of the scope to create should not be written. - /// The The settings of the writer. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - protected ResourceScope(ODataResource resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri) - : base(WriterState.Resource, resource, serializationInfo, navigationSource, resourceType, skipWriting, writerSettings, selectedProperties, odataUri) - { - } - } - - /// - /// Base class for DeletedResourceScope. - /// - internal class DeletedResourceScope : ResourceBaseScope - { - /// - /// Constructor to create a new resource scope. - /// - /// The resource for the new scope. - /// The serialization info for the current resource. - /// The navigation source we are going to write entities for. - /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). - /// The The settings of the writer. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - protected DeletedResourceScope(ODataDeletedResource resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType entityType, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri) - : base(WriterState.DeletedResource, resource, serializationInfo, navigationSource, entityType, false /*skipWriting*/, writerSettings, selectedProperties, odataUri) - { - } - } - - /// - /// A scope for a delta link. - /// - internal abstract class DeltaLinkScope : Scope - { - /// The serialization info for the current link. - private readonly ODataResourceSerializationInfo serializationInfo; - - /// - /// Fake entity type to be passed to context. - /// - private readonly EdmEntityType fakeEntityType = new EdmEntityType("MyNS", "Fake"); - - /// The type context to answer basic questions regarding the type info of the link. - private ODataResourceTypeContext typeContext; - - /// - /// Constructor to create a new delta link scope. - /// - /// The writer state of this scope. - /// The link for the new scope. - /// The serialization info for the current resource. - /// The navigation source we are going to write entities for. - /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). - /// The selected properties of this scope. - /// The ODataUri info of this scope. - protected DeltaLinkScope(WriterState state, ODataItem link, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType entityType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) - : base(state, link, navigationSource, entityType, /*skipWriting*/false, selectedProperties, odataUri, /*enableDelta*/ false) - { - Debug.Assert(link != null, "link != null"); - Debug.Assert( - state == WriterState.DeltaLink && link is ODataDeltaLink || - state == WriterState.DeltaDeletedLink && link is ODataDeltaDeletedLink, - "link must be either DeltaLink or DeltaDeletedLink."); - - this.serializationInfo = serializationInfo; - } - - /// - /// Gets or creates the type context to answer basic questions regarding the type info of the resource. - /// - /// Whether writing Json payload. Should always be true. - /// The type context to answer basic questions regarding the type info of the resource. - public ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse = true) - { - if (this.typeContext == null) - { - this.typeContext = ODataResourceTypeContext.Create( - this.serializationInfo, - this.NavigationSource, - EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource), - this.fakeEntityType, - writingResponse); - } - - return this.typeContext; - } - } - - /// - /// A scope for writing a single property within a resource. - /// - internal class PropertyInfoScope : Scope - { - /// - /// Constructor to create a new property scope. - /// - /// The property for the new scope. - /// The navigation source. - /// The structured type for the resource containing the property to be written. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - internal PropertyInfoScope(ODataPropertyInfo property, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) - : base(WriterState.Property, property, navigationSource, resourceType, /*skipWriting*/ false, selectedProperties, odataUri, /*enableDelta*/ true) - { - ValueWritten = false; - } - - public ODataPropertyInfo Property - { - get - { - Debug.Assert(this.Item is ODataProperty, "The item of a property scope is not an item."); - return this.Item as ODataProperty; - } - } - - internal bool ValueWritten { get; set; } - } - - /// - /// A scope for a nested resource info. - /// - internal class NestedResourceInfoScope : Scope - { - /// - /// Constructor to create a new nested resource info scope. - /// - /// The writer state for the new scope. - /// The nested resource info for the new scope. - /// The navigation source we are going to write resource set for. - /// The type for the items in the resource set to be written (or null if the entity set base type should be used). - /// true if the content of the scope to create should not be written. - /// The selected properties of this scope. - /// The ODataUri info of this scope. - /// The scope of the parent. - internal NestedResourceInfoScope(WriterState writerState, ODataNestedResourceInfo navLink, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, Scope parentScope) - : base(writerState, navLink, navigationSource, itemType, skipWriting, selectedProperties, odataUri, parentScope.EnableDelta) - { - this.parentScope = parentScope; - } - - /// Scope of the parent - protected Scope parentScope; - - /// - /// Clones this nested resource info scope and sets a new writer state. - /// - /// The to set. - /// The cloned nested resource info scope with the specified writer state. - internal virtual NestedResourceInfoScope Clone(WriterState newWriterState) - { - return new NestedResourceInfoScope(newWriterState, (ODataNestedResourceInfo)this.Item, this.NavigationSource, this.ItemType, this.SkipWriting, this.SelectedProperties, this.ODataUri, parentScope) - { - DerivedTypeConstraints = this.DerivedTypeConstraints, - }; - } - } - } -} +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.OData.Edm; +using Microsoft.OData.Evaluation; +using Microsoft.OData.Metadata; +using Microsoft.OData.UriParser; + +namespace Microsoft.OData +{ + /// + /// Base class for OData writers that verifies a proper sequence of write calls on the writer. + /// + internal abstract class ODataWriterCore : ODataWriter, IODataOutputInStreamErrorListener, IODataStreamListener + { + /// The writer validator to use. + protected readonly IWriterValidator WriterValidator; + + /// The output context to write to. + private readonly ODataOutputContext outputContext; + + /// True if the writer was created for writing a resourceSet; false when it was created for writing a resource. + private readonly bool writingResourceSet; + + /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer. + private readonly IODataReaderWriterListener listener; + + /// Stack of writer scopes to keep track of the current context of the writer. + private readonly ScopeStack scopeStack = new ScopeStack(); + + /// The number of entries which have been started but not yet ended. + private int currentResourceDepth; + + /// + /// Constructor. + /// + /// The output context to write to. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// True if the writer is created for writing a resourceSet; false when it is created for writing a resource. + /// True if the writer is created for writing a delta response; false otherwise. This parameter is ignored and will be removed in the next major release. + /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer. + protected ODataWriterCore( + ODataOutputContext outputContext, + IEdmNavigationSource navigationSource, + IEdmStructuredType resourceType, + bool writingResourceSet, + bool writingDelta = false, + IODataReaderWriterListener listener = null) + { + Debug.Assert(outputContext != null, "outputContext != null"); + + this.outputContext = outputContext; + this.writingResourceSet = writingResourceSet; + this.WriterValidator = outputContext.WriterValidator; + this.Version = outputContext.MessageWriterSettings.Version; + + if (navigationSource != null && resourceType == null) + { + resourceType = this.outputContext.EdmTypeResolver.GetElementType(navigationSource); + } + + ODataUri odataUri = outputContext.MessageWriterSettings.ODataUri.Clone(); + + // Remove key for top level resource + if (!writingResourceSet && odataUri != null && odataUri.Path != null) + { + odataUri.Path = odataUri.Path.TrimEndingKeySegment(); + } + + this.listener = listener; + + this.scopeStack.Push(new Scope(WriterState.Start, /*item*/null, navigationSource, resourceType, /*skipWriting*/false, outputContext.MessageWriterSettings.SelectedProperties, odataUri, /*enableDelta*/ true)); + this.CurrentScope.DerivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationSource)?.ToList(); + } + + /// + /// An enumeration representing the current state of the writer. + /// + internal enum WriterState + { + /// The writer is at the start; nothing has been written yet. + Start, + + /// The writer is currently writing a resource. + Resource, + + /// The writer is currently writing a resourceSet. + ResourceSet, + + /// The writer is currently writing a delta resource set. + DeltaResourceSet, + + /// The writer is currently writing a deleted resource. + DeletedResource, + + /// The writer is currently writing a delta link. + DeltaLink, + + /// The writer is currently writing a delta deleted link. + DeltaDeletedLink, + + /// The writer is currently writing a nested resource info (possibly an expanded link but we don't know yet). + /// + /// This state is used when a nested resource info was started but we didn't see any children for it yet. + /// + NestedResourceInfo, + + /// The writer is currently writing a nested resource info with content. + /// + /// This state is used when a nested resource info with either an entity reference link or expanded resourceSet/resource was written. + /// + NestedResourceInfoWithContent, + + /// The writer is currently writing a primitive value. + Primitive, + + /// The writer is currently writing a single property. + Property, + + /// The writer is currently writing a stream value. + Stream, + + /// The writer is currently writing a string value. + String, + + /// The writer has completed; nothing can be written anymore. + Completed, + + /// The writer is in error state; nothing can be written anymore. + Error + } + + /// + /// OData Version being written. + /// + internal ODataVersion? Version { get; } + + /// + /// The current scope for the writer. + /// + protected Scope CurrentScope + { + get + { + Debug.Assert(this.scopeStack.Count > 0, "We should have at least one active scope all the time."); + return this.scopeStack.Peek(); + } + } + + /// + /// The current state of the writer. + /// + protected WriterState State + { + get + { + return this.CurrentScope.State; + } + } + + /// + /// true if the writer should not write any input specified and should just skip it. + /// + protected bool SkipWriting + { + get + { + return this.CurrentScope.SkipWriting; + } + } + + /// + /// A flag indicating whether the writer is at the top level. + /// + protected bool IsTopLevel + { + get + { + Debug.Assert(this.State != WriterState.Start && this.State != WriterState.Completed, "IsTopLevel should only be called while writing the payload."); + + // there is the root scope at the top (when the writer has not started or has completed) + // and then the top-level scope (the top-level resource/resourceSet item) as the second scope on the stack + return this.scopeStack.Count == 2; + } + } + + /// + /// The scope level the writer is writing. + /// + protected int ScopeLevel + { + get { return this.scopeStack.Count; } + } + + /// + /// Returns the immediate parent link which is being expanded, or null if no such link exists + /// + protected ODataNestedResourceInfo ParentNestedResourceInfo + { + get + { + Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.ResourceSet || this.State == WriterState.DeltaResourceSet, "ParentNestedResourceInfo should only be called while writing a resource or a resourceSet."); + + Scope linkScope = this.scopeStack.ParentOrNull; + return linkScope == null ? null : (linkScope.Item as ODataNestedResourceInfo); + } + } + + /// + /// Returns the nested info that current resource belongs to. + /// + protected ODataNestedResourceInfo BelongingNestedResourceInfo + { + get + { + Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.ResourceSet || this.State == WriterState.DeletedResource || this.State == WriterState.DeltaResourceSet, "BelongingNestedResourceInfo should only be called while writing a (deleted) resource or a (delta) resourceSet."); + + Scope linkScope = this.scopeStack.ParentOrNull; + + // For single navigation + if (linkScope is NestedResourceInfoScope) + { + return linkScope.Item as ODataNestedResourceInfo; + } + else if (linkScope is ResourceSetBaseScope) + { + // For resource under collection of navigation/complex, parent is ResourceSetScope, so we need find parent of parent. + linkScope = this.scopeStack.ParentOfParent; + return linkScope == null ? null : (linkScope.Item as ODataNestedResourceInfo); + } + else + { + return null; + } + } + } + + /// + /// Returns the resource type of the immediate parent resource for which a nested resource info is being written. + /// + protected IEdmStructuredType ParentResourceType + { + get + { + Debug.Assert( + this.State == WriterState.NestedResourceInfo || this.State == WriterState.NestedResourceInfoWithContent, + "ParentResourceType should only be called while writing a nested resource info (with or without content), or within an untyped ResourceSet."); + Scope resourceScope = this.scopeStack.Parent; + return resourceScope.ResourceType; + } + } + + /// + /// Returns the navigation source of the immediate parent resource for which a nested resource info is being written. + /// + protected IEdmNavigationSource ParentResourceNavigationSource + { + get + { + Scope resourceScope = this.scopeStack.Parent; + return resourceScope == null ? null : resourceScope.NavigationSource; + } + } + + /// + /// Returns the parent scope of current scope. + /// + protected Scope ParentScope + { + get + { + Debug.Assert(this.scopeStack.Count > 1); + return this.scopeStack.Parent; + } + } + + /// + /// Returns the number of items seen so far on the current resource set scope. + /// + /// Can only be accessed on a resource set scope. + protected int ResourceSetScopeResourceCount + { + get + { + Debug.Assert(this.State == WriterState.ResourceSet, "ResourceSetScopeResourceCount should only be called while writing a resource set."); + return ((ResourceSetBaseScope)this.CurrentScope).ResourceCount; + } + } + + /// + /// Checker to detect duplicate property names. + /// + protected IDuplicatePropertyNameChecker DuplicatePropertyNameChecker + { + get + { + Debug.Assert( + this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.NestedResourceInfo || this.State == WriterState.NestedResourceInfoWithContent || this.State == WriterState.Property, + "PropertyAndAnnotationCollector should only be called while writing a resource or an (expanded or deferred) nested resource info."); + + ResourceBaseScope resourceScope; + switch (this.State) + { + case WriterState.DeletedResource: + case WriterState.Resource: + resourceScope = (ResourceBaseScope)this.CurrentScope; + break; + case WriterState.Property: + case WriterState.NestedResourceInfo: + case WriterState.NestedResourceInfoWithContent: + resourceScope = (ResourceBaseScope)this.scopeStack.Parent; + break; + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_PropertyAndAnnotationCollector)); + } + + return resourceScope.DuplicatePropertyNameChecker; + } + } + + /// + /// The structured type of the current resource. + /// + protected IEdmStructuredType ResourceType + { + get + { + return this.CurrentScope.ResourceType; + } + } + + /// + /// Returns the parent nested resource info scope of a resource in an expanded link (if it exists). + /// The resource can either be the content of the expanded link directly or nested inside a resourceSet. + /// + /// The parent navigation scope of a resource in an expanded link (if it exists). + protected NestedResourceInfoScope ParentNestedResourceInfoScope + { + get + { + Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.ResourceSet || this.State == WriterState.DeltaResourceSet, "ParentNestedResourceInfoScope should only be called while writing a resource or a resourceSet."); + Debug.Assert(this.scopeStack.Count >= 2, "We should have at least the resource scope and the start scope on the stack."); + + Scope parentScope = this.scopeStack.Parent; + if (parentScope.State == WriterState.Start) + { + // Top-level resource. + return null; + } + + if (parentScope.State == WriterState.ResourceSet || parentScope.State == WriterState.DeltaResourceSet) + { + Debug.Assert(this.scopeStack.Count >= 3, "We should have at least the resource scope, the resourceSet scope and the start scope on the stack."); + + // Get the resourceSet's parent + parentScope = this.scopeStack.ParentOfParent; + if (parentScope.State == WriterState.Start || + (parentScope.State == WriterState.ResourceSet && + parentScope.ResourceType != null && + parentScope.ResourceType.TypeKind == EdmTypeKind.Untyped)) + { + // Top-level resourceSet, or resourceSet within an untyped resourceSet. + return null; + } + } + + if (parentScope.State == WriterState.NestedResourceInfoWithContent) + { + // Get the scope of the nested resource info + return (NestedResourceInfoScope)parentScope; + } + + // The parent scope of a resource can only be a resourceSet or an expanded nav link + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_ParentNestedResourceInfoScope)); + } + } + + /// + /// Validator to validate consistency of collection items (or null if no such validator applies to the current scope). + /// + private ResourceSetWithoutExpectedTypeValidator CurrentResourceSetValidator + { + get + { + Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.Primitive, "CurrentCollectionValidator should only be called while writing a resource."); + + ResourceSetBaseScope resourceSetScope = this.ParentScope as ResourceSetBaseScope; + return resourceSetScope == null ? null : resourceSetScope.ResourceTypeValidator; + } + } + + /// + /// Flushes the write buffer to the underlying stream. + /// + public sealed override void Flush() + { + this.VerifyCanFlush(true); + + // Make sure we switch to writer state Error if an exception is thrown during flushing. + try + { + this.FlushSynchronously(); + } + catch + { + this.EnterScope(WriterState.Error, null); + throw; + } + } + + /// + /// Asynchronously flushes the write buffer to the underlying stream. + /// + /// A task instance that represents the asynchronous operation. + public sealed override Task FlushAsync() + { + this.VerifyCanFlush(false); + + // Make sure we switch to writer state Error if an exception is thrown during flushing. + return this.FlushAsynchronously().FollowOnFaultWith(t => this.EnterScope(WriterState.Error, null)); + } + + /// + /// Start writing a resourceSet. + /// + /// Resource Set/collection to write. + public sealed override void WriteStart(ODataResourceSet resourceSet) + { + this.VerifyCanWriteStartResourceSet(true, resourceSet); + this.WriteStartResourceSetImplementation(resourceSet); + } + + /// + /// Asynchronously start writing a resourceSet. + /// + /// Resource Set/collection to write. + /// A task instance that represents the asynchronous write operation. + public sealed override async Task WriteStartAsync(ODataResourceSet resourceSet) + { + await this.VerifyCanWriteStartResourceSetAsync(false, resourceSet) + .ConfigureAwait(false); + await this.WriteStartResourceSetImplementationAsync(resourceSet) + .ConfigureAwait(false); + } + + /// + /// Start writing a delta resource Set. + /// + /// Resource Set/collection to write. + public sealed override void WriteStart(ODataDeltaResourceSet deltaResourceSet) + { + this.VerifyCanWriteStartDeltaResourceSet(true, deltaResourceSet); + this.WriteStartDeltaResourceSetImplementation(deltaResourceSet); + } + + /// + /// Asynchronously start writing a delta resourceSet. + /// + /// Resource Set/collection to write. + /// A task instance that represents the asynchronous write operation. + public sealed override async Task WriteStartAsync(ODataDeltaResourceSet deltaResourceSet) + { + await this.VerifyCanWriteStartDeltaResourceSetAsync(false, deltaResourceSet) + .ConfigureAwait(false); + await this.WriteStartDeltaResourceSetImplementationAsync(deltaResourceSet) + .ConfigureAwait(false); + } + + /// + /// Start writing a resource. + /// + /// Resource/item to write. + public sealed override void WriteStart(ODataResource resource) + { + this.VerifyCanWriteStartResource(true, resource); + this.WriteStartResourceImplementation(resource); + } + + /// + /// Asynchronously start writing a resource. + /// + /// Resource/item to write. + /// A task instance that represents the asynchronous write operation. + public sealed override async Task WriteStartAsync(ODataResource resource) + { + this.VerifyCanWriteStartResource(false, resource); + await this.WriteStartResourceImplementationAsync(resource) + .ConfigureAwait(false); + } + + /// + /// Start writing a delta deleted resource. + /// + /// The delta deleted resource to write. + public sealed override void WriteStart(ODataDeletedResource deletedResource) + { + this.VerifyCanWriteStartDeletedResource(true, deletedResource); + this.WriteStartDeletedResourceImplementation(deletedResource); + } + + /// + /// Asynchronously write a delta deleted resource. + /// + /// The delta deleted resource to write. + /// A task instance that represents the asynchronous write operation. + public sealed override async Task WriteStartAsync(ODataDeletedResource deletedResource) + { + this.VerifyCanWriteStartDeletedResource(false, deletedResource); + await this.WriteStartDeletedResourceImplementationAsync(deletedResource) + .ConfigureAwait(false); + } + + /// + /// Writing a delta link. + /// + /// The delta link to write. + public override void WriteDeltaLink(ODataDeltaLink deltaLink) + { + this.VerifyCanWriteLink(true, deltaLink); + this.WriteDeltaLinkImplementation(deltaLink); + } + + /// + /// Asynchronously writing a delta link. + /// + /// The delta link to write. + /// A task instance that represents the asynchronous write operation. + public override async Task WriteDeltaLinkAsync(ODataDeltaLink deltaLink) + { + this.VerifyCanWriteLink(false, deltaLink); + await this.WriteDeltaLinkImplementationAsync(deltaLink) + .ConfigureAwait(false); + } + + /// + /// Writing a delta deleted link. + /// + /// The delta link to write. + public override void WriteDeltaDeletedLink(ODataDeltaDeletedLink deltaLink) + { + this.VerifyCanWriteLink(true, deltaLink); + this.WriteDeltaLinkImplementation(deltaLink); + } + + /// + /// Asynchronously writing a delta link. + /// + /// The delta link to write. + /// A task instance that represents the asynchronous write operation. + public override async Task WriteDeltaDeletedLinkAsync(ODataDeltaDeletedLink deltaLink) + { + this.VerifyCanWriteLink(false, deltaLink); + await this.WriteDeltaLinkImplementationAsync(deltaLink) + .ConfigureAwait(false); + } + + /// + /// Write a primitive value within an untyped collection. + /// + /// Primitive value to write. + public sealed override void WritePrimitive(ODataPrimitiveValue primitiveValue) + { + this.VerifyCanWritePrimitive(true, primitiveValue); + this.WritePrimitiveValueImplementation(primitiveValue); + } + + /// + /// Asynchronously write a primitive value. + /// + /// Primitive value to write. + /// A task instance that represents the asynchronous write operation. + public sealed override async Task WritePrimitiveAsync(ODataPrimitiveValue primitiveValue) + { + this.VerifyCanWritePrimitive(false, primitiveValue); + await this.WritePrimitiveValueImplementationAsync(primitiveValue) + .ConfigureAwait(false); + } + + /// Writes a primitive property within a resource. + /// The primitive property to write. + public sealed override void WriteStart(ODataPropertyInfo primitiveProperty) + { + this.VerifyCanWriteProperty(true, primitiveProperty); + this.WriteStartPropertyImplementation(primitiveProperty); + } + + /// Asynchronously write a primitive property within a resource. + /// A task instance that represents the asynchronous write operation. + /// The primitive property to write. + public sealed override async Task WriteStartAsync(ODataPropertyInfo primitiveProperty) + { + this.VerifyCanWriteProperty(false, primitiveProperty); + await this.WriteStartPropertyImplementationAsync(primitiveProperty) + .ConfigureAwait(false); + } + + /// Creates a stream for writing a binary value. + /// A stream to write a binary value to. + public sealed override Stream CreateBinaryWriteStream() + { + this.VerifyCanCreateWriteStream(true); + return this.CreateWriteStreamImplementation(); + } + + /// Asynchronously creates a stream for writing a binary value. + /// A task that represents the asynchronous operation. + /// The value of the TResult parameter contains a to write a binary value to. + public sealed override async Task CreateBinaryWriteStreamAsync() + { + this.VerifyCanCreateWriteStream(false); + return await this.CreateWriteStreamImplementationAsync() + .ConfigureAwait(false); + } + + /// Creates a TextWriter for writing a string value. + /// A TextWriter to write a string value to. + public sealed override TextWriter CreateTextWriter() + { + this.VerifyCanCreateTextWriter(true); + return this.CreateTextWriterImplementation(); + } + + /// Asynchronously creates a for writing a string value. + /// A task that represents the asynchronous operation. + /// The value of the TResult parameter contains a to write a string value to. + public sealed override async Task CreateTextWriterAsync() + { + this.VerifyCanCreateWriteStream(false); + return await this.CreateTextWriterImplementationAsync() + .ConfigureAwait(false); + } + + /// + /// Start writing a nested resource info. + /// + /// Navigation link to write. + public sealed override void WriteStart(ODataNestedResourceInfo nestedResourceInfo) + { + this.VerifyCanWriteStartNestedResourceInfo(true, nestedResourceInfo); + this.WriteStartNestedResourceInfoImplementation(nestedResourceInfo); + } + + + /// + /// Asynchronously start writing a nested resource info. + /// + /// Navigation link to writer. + /// A task instance that represents the asynchronous write operation. + public sealed override async Task WriteStartAsync(ODataNestedResourceInfo nestedResourceInfo) + { + this.VerifyCanWriteStartNestedResourceInfo(false, nestedResourceInfo); + // Currently, no asynchronous operation is involved when commencing with writing a nested resource info + await TaskUtils.GetTaskForSynchronousOperation( + () => this.WriteStartNestedResourceInfoImplementation(nestedResourceInfo)).ConfigureAwait(false); + } + + /// + /// Finish writing a resourceSet/resource/nested resource info. + /// + public sealed override void WriteEnd() + { + this.VerifyCanWriteEnd(true); + this.WriteEndImplementation(); + if (this.CurrentScope.State == WriterState.Completed) + { + // Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state. + this.Flush(); + } + } + + + /// + /// Asynchronously finish writing a resourceSet/resource/nested resource info. + /// + /// A task instance that represents the asynchronous write operation. + public sealed override async Task WriteEndAsync() + { + this.VerifyCanWriteEnd(false); + await this.WriteEndImplementationAsync() + .ConfigureAwait(false); + + if (this.CurrentScope.State == WriterState.Completed) + { + await this.FlushAsync() + .ConfigureAwait(false); + } + } + + /// + /// Writes an entity reference link, which is used to represent binding to an existing resource in a request payload. + /// + /// The entity reference link to write. + /// + /// This method can only be called for writing request messages. The entity reference link must be surrounded + /// by a navigation link written through WriteStart/WriteEnd. + /// The will be ignored in that case and the Uri from the will be used + /// as the binding URL to be written. + /// + public sealed override void WriteEntityReferenceLink(ODataEntityReferenceLink entityReferenceLink) + { + this.VerifyCanWriteEntityReferenceLink(entityReferenceLink, true); + this.WriteEntityReferenceLinkImplementation(entityReferenceLink); + } + + + /// + /// Asynchronously writes an entity reference link, which is used to represent binding to an existing resource in a request payload. + /// + /// The entity reference link to write. + /// A task instance that represents the asynchronous write operation. + /// + /// This method can only be called for writing request messages. The entity reference link must be surrounded + /// by a navigation link written through WriteStart/WriteEnd. + /// The will be ignored in that case and the Uri from the will be used + /// as the binding URL to be written. + /// + public sealed override async Task WriteEntityReferenceLinkAsync(ODataEntityReferenceLink entityReferenceLink) + { + this.VerifyCanWriteEntityReferenceLink(entityReferenceLink, false); + await this.WriteEntityReferenceLinkImplementationAsync(entityReferenceLink) + .ConfigureAwait(false); + } + + /// + /// This method notifies the listener, that an in-stream error is to be written. + /// + /// + /// This listener can choose to fail, if the currently written payload doesn't support in-stream error at this position. + /// If the listener returns, the writer should not allow any more writing, since the in-stream error is the last thing in the payload. + /// + void IODataOutputInStreamErrorListener.OnInStreamError() + { + this.VerifyNotDisposed(); + + // We're in a completed state trying to write an error (we can't write error after the payload was finished as it might + // introduce another top-level element in XML) + if (this.State == WriterState.Completed) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), WriterState.Error.ToString())); + } + + this.StartPayloadInStartState(); + this.EnterScope(WriterState.Error, this.CurrentScope.Item); + } + + /// + async Task IODataOutputInStreamErrorListener.OnInStreamErrorAsync() + { + this.VerifyNotDisposed(); + + // We're in a completed state trying to write an error (we can't write error after the payload was finished as it might + // introduce another top-level element in XML) + if (this.State == WriterState.Completed) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), WriterState.Error.ToString())); + } + + await this.StartPayloadInStartStateAsync() + .ConfigureAwait(false); + this.EnterScope(WriterState.Error, this.CurrentScope.Item); + } + + /// + /// This method is called when a stream is requested. It is a no-op. + /// + void IODataStreamListener.StreamRequested() + { + } + + /// + /// This method is called when an async stream is requested. It is a no-op. + /// + /// A task for method called when a stream is requested. + Task IODataStreamListener.StreamRequestedAsync() + { + return TaskUtils.CompletedTask; + } + + /// + /// This method is called when a stream is disposed. + /// + void IODataStreamListener.StreamDisposed() + { + Debug.Assert(this.State == WriterState.Stream || this.State == WriterState.String, "Stream was disposed when not in WriterState.Stream state."); + + // Complete writing the stream + if (this.State == WriterState.Stream) + { + this.EndBinaryStream(); + } + else if (this.State == WriterState.String) + { + this.EndTextWriter(); + } + + this.LeaveScope(); + } + + /// + /// This method is called asynchronously when a stream is disposed. + /// + /// A task that represents the asynchronous operation. + async Task IODataStreamListener.StreamDisposedAsync() + { + Debug.Assert(this.State == WriterState.Stream || this.State == WriterState.String, + "Stream was disposed when not in WriterState.Stream state."); + + // Complete writing the stream + if (this.State == WriterState.Stream) + { + await this.EndBinaryStreamAsync() + .ConfigureAwait(false); + } + else if (this.State == WriterState.String) + { + await this.EndTextWriterAsync() + .ConfigureAwait(false); + } + + await this.LeaveScopeAsync() + .ConfigureAwait(false); + } + + /// + /// Get instance of the parent resource scope + /// + /// + /// The parent resource scope + /// Or null if there is no parent resource scope + /// + protected ResourceScope GetParentResourceScope() + { + ScopeStack scopeStack = new ScopeStack(); + Scope parentResourceScope = null; + + if (this.scopeStack.Count > 0) + { + // pop current scope and push into scope stack + scopeStack.Push(this.scopeStack.Pop()); + } + + while (this.scopeStack.Count > 0) + { + Scope scope = this.scopeStack.Pop(); + scopeStack.Push(scope); + + if (scope is ResourceScope) + { + parentResourceScope = scope; + break; + } + } + + while (scopeStack.Count > 0) + { + Scope scope = scopeStack.Pop(); + this.scopeStack.Push(scope); + } + + return parentResourceScope as ResourceScope; + } + + /// + /// Determines whether a given writer state is considered an error state. + /// + /// The writer state to check. + /// True if the writer state is an error state; otherwise false. + protected static bool IsErrorState(WriterState state) + { + return state == WriterState.Error; + } + + /// + /// Check if the object has been disposed; called from all public API methods. Throws an ObjectDisposedException if the object + /// has already been disposed. + /// + protected abstract void VerifyNotDisposed(); + + /// + /// Flush the output. + /// + protected abstract void FlushSynchronously(); + + + /// + /// Flush the output. + /// + /// Task representing the pending flush operation. + protected abstract Task FlushAsynchronously(); + + /// + /// Start writing an OData payload. + /// + protected abstract void StartPayload(); + + /// + /// Start writing a resource. + /// + /// The resource to write. + protected abstract void StartResource(ODataResource resource); + + /// + /// Finish writing a resource. + /// + /// The resource to write. + protected abstract void EndResource(ODataResource resource); + + /// + /// Start writing a single property. + /// + /// The property to write. + protected virtual void StartProperty(ODataPropertyInfo property) + { + throw new NotImplementedException(); + } + + /// + /// Finish writing a property. + /// + /// The property to write. + protected virtual void EndProperty(ODataPropertyInfo property) + { + throw new NotImplementedException(); + } + + /// + /// Start writing a resourceSet. + /// + /// The resourceSet to write. + protected abstract void StartResourceSet(ODataResourceSet resourceSet); + + /// + /// Start writing a delta resource set. + /// + /// The delta resource set to write. + protected virtual void StartDeltaResourceSet(ODataDeltaResourceSet deltaResourceSet) + { + throw new NotImplementedException(); + } + + /// + /// Start writing a deleted resource. + /// + /// The deleted entry to write. + protected virtual void StartDeletedResource(ODataDeletedResource deletedEntry) + { + throw new NotImplementedException(); + } + + /// + /// Write a delta link or delta deleted link. + /// + /// The deleted entry to write. + protected virtual void StartDeltaLink(ODataDeltaLinkBase deltaLink) + { + throw new NotImplementedException(); + } + + /// + /// Create a stream to write a binary value. + /// + /// A stream for writing the binary value. + protected virtual Stream StartBinaryStream() + { + throw new NotImplementedException(); + } + + /// + /// Finish writing a stream. + /// + protected virtual void EndBinaryStream() + { + throw new NotImplementedException(); + } + + /// + /// Create a TextWriter to write a string value. + /// + /// A TextWriter for writing the string value. + protected virtual TextWriter StartTextWriter() + { + throw new NotImplementedException(); + } + + /// + /// Finish writing a string value. + /// + protected virtual void EndTextWriter() + { + throw new NotImplementedException(); + } + + /// + /// Finish writing an OData payload. + /// + protected abstract void EndPayload(); + + /// + /// Finish writing a resourceSet. + /// + /// The resourceSet to write. + protected abstract void EndResourceSet(ODataResourceSet resourceSet); + + /// + /// Finish writing a delta resource set. + /// + /// The delta resource set to write. + protected virtual void EndDeltaResourceSet(ODataDeltaResourceSet deltaResourceSet) + { + throw new NotImplementedException(); + } + + /// + /// Finish writing a deleted resource. + /// + /// The delta resource set to write. + protected virtual void EndDeletedResource(ODataDeletedResource deletedResource) + { + throw new NotImplementedException(); + } + + /// + /// Write a primitive value within an untyped collection. + /// + /// The primitive value to write. + protected virtual void WritePrimitiveValue(ODataPrimitiveValue primitiveValue) + { + throw new NotImplementedException(); + } + + /// + /// Write a deferred (non-expanded) nested resource info. + /// + /// The nested resource info to write. + protected abstract void WriteDeferredNestedResourceInfo(ODataNestedResourceInfo nestedResourceInfo); + + /// + /// Start writing a nested resource info with content. + /// + /// The nested resource info to write. + protected abstract void StartNestedResourceInfoWithContent(ODataNestedResourceInfo nestedResourceInfo); + + /// + /// Finish writing a nested resource info with content. + /// + /// The nested resource info to write. + protected abstract void EndNestedResourceInfoWithContent(ODataNestedResourceInfo nestedResourceInfo); + + /// + /// Write an entity reference link into a navigation link content. + /// + /// The parent navigation link which is being written around the entity reference link. + /// The entity reference link to write. + protected abstract void WriteEntityReferenceInNavigationLinkContent(ODataNestedResourceInfo parentNestedResourceInfo, ODataEntityReferenceLink entityReferenceLink); + + /// + /// Create a new resource set scope. + /// + /// The resource set for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource set is for an undeclared property + /// The newly create scope. + protected abstract ResourceSetScope CreateResourceSetScope(ODataResourceSet resourceSet, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared); + + /// + /// Create a new delta resource set scope. + /// + /// The delta resource set for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource set is for an undeclared property + /// The newly create scope. + protected virtual DeltaResourceSetScope CreateDeltaResourceSetScope(ODataDeltaResourceSet deltaResourceSet, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared) + { + throw new NotImplementedException(); + } + + /// + /// Create a new resource scope. + /// + /// The resource for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource is for an undeclared property + /// The newly create scope. + protected abstract ResourceScope CreateResourceScope(ODataResource resource, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared); + + /// + /// Create a new resource scope. + /// + /// The (deleted) resource for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource is for an undeclared property + /// The newly create scope. + protected virtual DeletedResourceScope CreateDeletedResourceScope(ODataDeletedResource resource, IEdmNavigationSource navigationSource, IEdmEntityType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared) + { + throw new NotImplementedException(); + } + + /// + /// Create a new property scope. + /// + /// The property for the new scope. + /// The navigation source. + /// The structured type for the resource containing the property to be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// The newly created property scope. + protected virtual PropertyInfoScope CreatePropertyInfoScope(ODataPropertyInfo property, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + { + throw new NotImplementedException(); + } + + /// + /// Create a new delta link scope. + /// + /// The link for the new scope. + /// The navigation source we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// The newly create scope. + protected virtual DeltaLinkScope CreateDeltaLinkScope(ODataDeltaLinkBase link, IEdmNavigationSource navigationSource, IEdmEntityType entityType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + { + throw new NotImplementedException(); + } + + /// + /// Gets the serialization info for the given resource. + /// + /// The resource to get the serialization info for. + /// The serialization info for the given resource. + protected ODataResourceSerializationInfo GetResourceSerializationInfo(ODataResourceBase resource) + { + // Need to check for null for the resource since we can be writing a null reference to a navigation property. + ODataResourceSerializationInfo serializationInfo = resource == null ? null : resource.SerializationInfo; + + // Always try to use the serialization info from the resource first. If it is not found on the resource, use the one inherited from the parent resourceSet. + // Note that we don't try to guard against inconsistent serialization info between entries and their parent resourceSet. + if (serializationInfo != null) + { + return serializationInfo; + } + + ODataResourceSetBase resourceSet = this.CurrentScope.Item as ODataResourceSetBase; + if (resourceSet != null) + { + return resourceSet.SerializationInfo; + } + + return null; + } + + /// + /// Gets the serialization info for the given delta link. + /// + /// The resource to get the serialization info for. + /// The serialization info for the given resource. + protected ODataResourceSerializationInfo GetLinkSerializationInfo(ODataItem item) + { + Debug.Assert(item != null, "item != null"); + + ODataDeltaSerializationInfo deltaSerializationInfo = null; + ODataResourceSerializationInfo resourceSerializationInfo = null; + + ODataDeltaLink deltaLink = item as ODataDeltaLink; + if (deltaLink != null) + { + deltaSerializationInfo = deltaLink.SerializationInfo; + } + + ODataDeltaDeletedLink deltaDeletedLink = item as ODataDeltaDeletedLink; + if (deltaDeletedLink != null) + { + deltaSerializationInfo = deltaDeletedLink.SerializationInfo; + } + + if (deltaSerializationInfo == null) + { + DeltaResourceSetScope parentDeltaResourceSetScope = this.CurrentScope as DeltaResourceSetScope; + if (parentDeltaResourceSetScope != null) + { + ODataDeltaResourceSet resourceSet = (ODataDeltaResourceSet)parentDeltaResourceSetScope.Item; + Debug.Assert(resourceSet != null, "resourceSet != null"); + + ODataResourceSerializationInfo deltaSetSerializationInfo = resourceSet.SerializationInfo; + if (deltaSetSerializationInfo != null) + { + resourceSerializationInfo = deltaSetSerializationInfo; + } + } + } + else + { + resourceSerializationInfo = new ODataResourceSerializationInfo() + { + NavigationSourceName = deltaSerializationInfo.NavigationSourceName + }; + } + + return resourceSerializationInfo; + } + + /// + /// Creates a new nested resource info scope. + /// + /// The writer state for the new scope. + /// The nested resource info for the new scope. + /// The navigation source we are going to write entities for. + /// The type for the items in the resourceSet to be written (or null if the resource set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// The newly created nested resource info scope. + protected virtual NestedResourceInfoScope CreateNestedResourceInfoScope( + WriterState writerState, + ODataNestedResourceInfo navLink, + IEdmNavigationSource navigationSource, + IEdmType itemType, + bool skipWriting, + SelectedPropertiesNode selectedProperties, + ODataUri odataUri) + { + Debug.Assert(this.CurrentScope != null, "Creating a nested resource info scope with a null parent scope."); + return new NestedResourceInfoScope(writerState, navLink, navigationSource, itemType, skipWriting, selectedProperties, odataUri, this.CurrentScope); + } + + /// + /// Place where derived writers can perform custom steps before the resource is written, at the beginning of WriteStartEntryImplementation. + /// + /// The ResourceScope. + /// Resource to write. + /// True if writing response. + /// The selected properties of this scope. + protected virtual void PrepareResourceForWriteStart(ResourceScope resourceScope, ODataResource resource, bool writingResponse, SelectedPropertiesNode selectedProperties) + { + // No-op Atom and Verbose JSON. The JSON Light writer will override this method and inject the appropriate metadata builder + // into the resource before writing. + // Actually we can inject the metadata builder in here and + // remove virtual from this method. + } + + /// + /// Place where derived writers can perform custom steps before the deleted resource is written, at the beginning of WriteStartEntryImplementation. + /// + /// The ResourceScope. + /// Resource to write. + /// True if writing response. + /// The selected properties of this scope. + protected virtual void PrepareDeletedResourceForWriteStart(DeletedResourceScope resourceScope, ODataDeletedResource deletedResource, bool writingResponse, SelectedPropertiesNode selectedProperties) + { + // No-op Atom and Verbose JSON. The JSON Light writer will override this method and inject the appropriate metadata builder + // into the resource before writing. + // Actually we can inject the metadata builder in here and + // remove virtual from this method. + } + + /// + /// Gets the type of the resource and validates it against the model. + /// + /// The resource to get the type for. + /// The validated structured type. + protected IEdmStructuredType GetResourceType(ODataResourceBase resource) + { + return TypeNameOracle.ResolveAndValidateTypeFromTypeName( + this.outputContext.Model, + this.CurrentScope.ResourceType, + resource.TypeName, + this.WriterValidator); + } + + /// + /// Gets the element type of the resource set and validates it against the model. + /// + /// The resource set to get the element type for. + /// The validated structured element type. + protected IEdmStructuredType GetResourceSetType(ODataResourceSetBase resourceSet) + { + return TypeNameOracle.ResolveAndValidateTypeFromTypeName( + this.outputContext.Model, + this.CurrentScope.ResourceType, + EdmLibraryExtensions.GetCollectionItemTypeName(resourceSet.TypeName), + this.WriterValidator); + } + + /// + /// Validates that the ODataResourceSet.DeltaLink is null for the given expanded resourceSet. + /// + /// The expanded resourceSet in question. + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "An instance field is used in a debug assert.")] + protected void ValidateNoDeltaLinkForExpandedResourceSet(ODataResourceSet resourceSet) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + Debug.Assert( + this.ParentNestedResourceInfo != null && (!this.ParentNestedResourceInfo.IsCollection.HasValue || this.ParentNestedResourceInfo.IsCollection.Value == true), + "This should only be called when writing an expanded resourceSet."); + + if (resourceSet.DeltaLink != null) + { + throw new ODataException(Strings.ODataWriterCore_DeltaLinkNotSupportedOnExpandedResourceSet); + } + } + + /// + /// Asynchronously start writing an OData payload. + /// + /// A task that represents the asynchronous write operation. + protected abstract Task StartPayloadAsync(); + + /// + /// Asynchronously finish writing an OData payload. + /// + /// A task that represents the asynchronous write operation. + protected abstract Task EndPayloadAsync(); + + /// + /// Asynchronously start writing a resource. + /// + /// The resource to write. + /// A task that represents the asynchronous write operation. + protected abstract Task StartResourceAsync(ODataResource resource); + + /// + /// Asynchronously finish writing a resource. + /// + /// The resource to write. + /// A task that represents the asynchronous write operation. + protected abstract Task EndResourceAsync(ODataResource resource); + + /// + /// Asynchronously start writing a single property. + /// + /// The property to write. + /// A task that represents the asynchronous write operation. + protected virtual Task StartPropertyAsync(ODataPropertyInfo property) + { + throw new NotImplementedException(); + } + + /// + /// Asynchronously finish writing a property. + /// + /// The property to write. + /// A task that represents the asynchronous write operation. + protected virtual Task EndPropertyAsync(ODataPropertyInfo property) + { + throw new NotImplementedException(); + } + + /// + /// Asynchronously start writing a resourceSet. + /// + /// The resourceSet to write. + /// A task that represents the asynchronous write operation. + protected abstract Task StartResourceSetAsync(ODataResourceSet resourceSet); + + /// + /// Asynchronously finish writing a resourceSet. + /// + /// The resourceSet to write. + /// A task that represents the asynchronous write operation. + protected abstract Task EndResourceSetAsync(ODataResourceSet resourceSet); + + /// + /// Asynchronously start writing a delta resource set. + /// + /// The delta resource set to write. + /// A task that represents the asynchronous write operation. + protected virtual Task StartDeltaResourceSetAsync(ODataDeltaResourceSet deltaResourceSet) + { + throw new NotImplementedException(); + } + + /// + /// Asynchronously finish writing a delta resource set. + /// + /// The delta resource set to write. + /// A task that represents the asynchronous write operation. + protected virtual Task EndDeltaResourceSetAsync(ODataDeltaResourceSet deltaResourceSet) + { + throw new NotImplementedException(); + } + + /// + /// Asynchronously start writing a deleted resource. + /// + /// The deleted entry to write. + /// A task that represents the asynchronous write operation. + protected virtual Task StartDeletedResourceAsync(ODataDeletedResource deletedEntry) + { + throw new NotImplementedException(); + } + + /// + /// Asynchronously finish writing a deleted resource. + /// + /// The delta resource set to write. + /// A task that represents the asynchronous write operation. + protected virtual Task EndDeletedResourceAsync(ODataDeletedResource deletedResource) + { + throw new NotImplementedException(); + } + + /// + /// Asynchronously write a delta link or delta deleted link. + /// + /// The deleted entry to write. + /// A task that represents the asynchronous write operation. + protected virtual Task StartDeltaLinkAsync(ODataDeltaLinkBase deltaLink) + { + throw new NotImplementedException(); + } + + /// + /// Asynchronously create a to write a binary value. + /// + /// A task that represents the asynchronous write operation. + /// The value of the TResult parameter contains the for writing the binary value. + protected virtual Task StartBinaryStreamAsync() + { + throw new NotImplementedException(); + } + + /// + /// Asynchronously finish writing a stream. + /// + /// A task that represents the asynchronous write operation. + protected virtual Task EndBinaryStreamAsync() + { + throw new NotImplementedException(); + } + + /// + /// Asynchronously create a to write a string value. + /// + /// A task that represents the asynchronous operation. + /// The value of the TResult parameter contains the for writing a string value. + protected virtual Task StartTextWriterAsync() + { + throw new NotImplementedException(); + } + + /// + /// Asynchronously finish writing a string value. + /// + /// A task that represents the asynchronous write operation. + protected virtual Task EndTextWriterAsync() + { + throw new NotImplementedException(); + } + + /// + /// Asynchronously write a primitive value within an untyped collection. + /// + /// The primitive value to write. + /// A task that represents the asynchronous write operation. + protected virtual Task WritePrimitiveValueAsync(ODataPrimitiveValue primitiveValue) + { + throw new NotImplementedException(); + } + + /// + /// Asynchronously write a deferred (non-expanded) nested resource info. + /// + /// The nested resource info to write. + /// A task that represents the asynchronous write operation. + protected abstract Task WriteDeferredNestedResourceInfoAsync(ODataNestedResourceInfo nestedResourceInfo); + + /// + /// Asynchronously start writing a nested resource info with content. + /// + /// The nested resource info to write. + /// A task that represents the asynchronous write operation. + protected abstract Task StartNestedResourceInfoWithContentAsync(ODataNestedResourceInfo nestedResourceInfo); + + /// + /// Asynchronously finish writing a nested resource info with content. + /// + /// The nested resource info to write. + /// A task that represents the asynchronous write operation. + protected abstract Task EndNestedResourceInfoWithContentAsync(ODataNestedResourceInfo nestedResourceInfo); + + /// + /// Asynchronously write an entity reference link into a navigation link content. + /// + /// The parent navigation link which is being written around the entity reference link. + /// The entity reference link to write. + /// A task that represents the asynchronous write operation. + protected abstract Task WriteEntityReferenceInNavigationLinkContentAsync( + ODataNestedResourceInfo parentNestedResourceInfo, + ODataEntityReferenceLink entityReferenceLink); + + /// + /// Place where derived writers can perform custom steps before the resource is written, + /// at the beginning of WriteStartAsync(ODataResource). + /// + /// The ResourceScope. + /// Resource to write. + /// True if writing response. + /// The selected properties of this scope. + protected virtual Task PrepareResourceForWriteStartAsync( + ResourceScope resourceScope, + ODataResource resource, + bool writingResponse, + SelectedPropertiesNode selectedProperties) + { + // No-op Atom and Verbose JSON. + // ODataJsonLightWriter will override this method and inject the appropriate metadata builder + // into the resource before writing. + return TaskUtils.CompletedTask; + } + + /// + /// Place where derived writers can perform custom steps before the deleted resource is written, + /// at the beginning of WriteStartAsync(ODataDeletedResource). + /// + /// The ResourceScope. + /// Resource to write. + /// True if writing response. + /// The selected properties of this scope. + protected virtual Task PrepareDeletedResourceForWriteStartAsync( + DeletedResourceScope resourceScope, + ODataDeletedResource deletedResource, + bool writingResponse, + SelectedPropertiesNode selectedProperties) + { + // No-op Atom and Verbose JSON. + // ODataJsonLightWriter will override this method and inject the appropriate metadata builder + // into the resource before writing. + return TaskUtils.CompletedTask; + } + + /// + /// Verifies that calling WriteStart resourceSet is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Resource Set/collection to write. + private void VerifyCanWriteStartResourceSet(bool synchronousCall, ODataResourceSet resourceSet) + { + ExceptionUtils.CheckArgumentNotNull(resourceSet, "resourceSet"); + + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + this.StartPayloadInStartState(); + } + + /// + /// Start writing a resourceSet - implementation of the actual functionality. + /// + /// The resource set to write. + private void WriteStartResourceSetImplementation(ODataResourceSet resourceSet) + { + this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.ResourceSet, resourceSet); + this.EnterScope(WriterState.ResourceSet, resourceSet); + + if (!this.SkipWriting) + { + this.InterceptException( + (thisParam, resourceSetParam) => + { + // Verify query count + if (resourceSetParam.Count.HasValue) + { + // Check that Count is not set for requests + if (!thisParam.outputContext.WritingResponse) + { + thisParam.ThrowODataException(Strings.ODataWriterCore_QueryCountInRequest, resourceSetParam); + } + + // Verify version requirements + } + + thisParam.StartResourceSet(resourceSetParam); + }, resourceSet); + } + } + + /// + /// Verifies that calling WriteStart deltaResourceSet is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Resource Set/collection to write. + private void VerifyCanWriteStartDeltaResourceSet(bool synchronousCall, ODataDeltaResourceSet deltaResourceSet) + { + ExceptionUtils.CheckArgumentNotNull(deltaResourceSet, "deltaResourceSet"); + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + this.StartPayloadInStartState(); + } + + /// + /// Start writing a delta resource set - implementation of the actual functionality. + /// + /// The delta resource Set to write. + private void WriteStartDeltaResourceSetImplementation(ODataDeltaResourceSet deltaResourceSet) + { + this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.ResourceSet, deltaResourceSet); + this.EnterScope(WriterState.DeltaResourceSet, deltaResourceSet); + + this.InterceptException( + (thisParam, deltaResourceSetParam) => + { + // Check that links are not set for requests + if (!thisParam.outputContext.WritingResponse) + { + if (deltaResourceSetParam.NextPageLink != null) + { + thisParam.ThrowODataException(Strings.ODataWriterCore_QueryNextLinkInRequest, deltaResourceSetParam); + } + + if (deltaResourceSetParam.DeltaLink != null) + { + thisParam.ThrowODataException(Strings.ODataWriterCore_QueryDeltaLinkInRequest, deltaResourceSetParam); + } + } + + thisParam.StartDeltaResourceSet(deltaResourceSetParam); + }, deltaResourceSet); + } + + /// + /// Verifies that calling WriteStart resource is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Resource/item to write. + private void VerifyCanWriteStartResource(bool synchronousCall, ODataResource resource) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that calling WriteDeletedResource is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Resource/item to write. + private void VerifyCanWriteStartDeletedResource(bool synchronousCall, ODataDeletedResource resource) + { + ExceptionUtils.CheckArgumentNotNull(resource, "resource"); + + this.VerifyWritingDelta(); + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Start writing a resource - implementation of the actual functionality. + /// + /// Resource/item to write. + private void WriteStartResourceImplementation(ODataResource resource) + { + this.StartPayloadInStartState(); + this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.Resource, resource); + this.EnterScope(WriterState.Resource, resource); + if (!this.SkipWriting) + { + this.IncreaseResourceDepth(); + this.InterceptException( + (thisParam, resourceParam) => + { + if (resourceParam != null) + { + ResourceScope resourceScope = (ResourceScope)thisParam.CurrentScope; + thisParam.ValidateResourceForResourceSet(resourceParam, resourceScope); + thisParam.PrepareResourceForWriteStart( + resourceScope, + resourceParam, + thisParam.outputContext.WritingResponse, + resourceScope.SelectedProperties); + } + + thisParam.StartResource(resourceParam); + }, resource); + } + } + + /// + /// Start writing a delta deleted resource - implementation of the actual functionality. + /// + /// Resource/item to write. + private void WriteStartDeletedResourceImplementation(ODataDeletedResource resource) + { + Debug.Assert(resource != null, "resource != null"); + + this.StartPayloadInStartState(); + this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.Resource, resource); + this.EnterScope(WriterState.DeletedResource, resource); + this.IncreaseResourceDepth(); + + this.InterceptException( + (thisParam, resourceParam) => + { + DeletedResourceScope resourceScope = thisParam.CurrentScope as DeletedResourceScope; + thisParam.ValidateResourceForResourceSet(resourceParam, resourceScope); + thisParam.PrepareDeletedResourceForWriteStart( + resourceScope, + resourceParam, + thisParam.outputContext.WritingResponse, + resourceScope.SelectedProperties); + thisParam.StartDeletedResource(resourceParam); + }, resource); + } + + /// + /// Verifies that calling WriteStart for a property is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Primitive property to write. + private void VerifyCanWriteProperty(bool synchronousCall, ODataPropertyInfo property) + { + ExceptionUtils.CheckArgumentNotNull(property, "property"); + + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Start writing a property - implementation of the actual functionality. + /// + /// Property to write. + private void WriteStartPropertyImplementation(ODataPropertyInfo property) + { + this.EnterScope(WriterState.Property, property); + if (!this.SkipWriting) + { + this.InterceptException( + (thisParam, propertyParam) => + { + thisParam.StartProperty(propertyParam); + if (propertyParam is ODataProperty) + { + PropertyInfoScope scope = thisParam.CurrentScope as PropertyInfoScope; + Debug.Assert(scope != null, "Scope for ODataPropertyInfo is not ODataPropertyInfoScope"); + scope.ValueWritten = true; + } + }, property); + } + } + + /// + /// Start writing a delta link or delta deleted link - implementation of the actual functionality. + /// + /// Delta (deleted) link to write. + private void WriteDeltaLinkImplementation(ODataDeltaLinkBase deltaLink) + { + this.EnterScope(deltaLink is ODataDeltaLink ? WriterState.DeltaLink : WriterState.DeltaDeletedLink, deltaLink); + this.StartDeltaLink(deltaLink); + this.WriteEnd(); + } + + /// + /// Start writing a delta link or delta deleted link - implementation of the actual functionality. + /// + /// Delta (deleted) link to write. + /// The task. + private async Task WriteDeltaLinkImplementationAsync(ODataDeltaLinkBase deltaLink) + { + EnterScope(deltaLink is ODataDeltaLink ? WriterState.DeltaLink : WriterState.DeltaDeletedLink, deltaLink); + await this.StartDeltaLinkAsync(deltaLink) + .ConfigureAwait(false); + await this.WriteEndAsync() + .ConfigureAwait(false); + } + + /// + /// Verifies that calling WriteStart nested resource info is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Navigation link to write. + private void VerifyCanWriteStartNestedResourceInfo(bool synchronousCall, ODataNestedResourceInfo nestedResourceInfo) + { + ExceptionUtils.CheckArgumentNotNull(nestedResourceInfo, "nestedResourceInfo"); + + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Start writing a nested resource info - implementation of the actual functionality. + /// + /// Navigation link to write. + private void WriteStartNestedResourceInfoImplementation(ODataNestedResourceInfo nestedResourceInfo) + { + this.EnterScope(WriterState.NestedResourceInfo, nestedResourceInfo); + + // If the parent resource has a metadata builder, use that metadatabuilder on the nested resource info as well. + Debug.Assert(this.scopeStack.Parent != null, "Navigation link scopes must have a parent scope."); + Debug.Assert(this.scopeStack.Parent.Item is ODataResourceBase, "The parent of a nested resource info scope should always be a resource"); + ODataResourceBase parentResource = (ODataResourceBase)this.scopeStack.Parent.Item; + if (parentResource.MetadataBuilder != null) + { + nestedResourceInfo.MetadataBuilder = parentResource.MetadataBuilder; + } + } + + /// + /// Verifies that calling WritePrimitive is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Primitive value to write. + private void VerifyCanWritePrimitive(bool synchronousCall, ODataPrimitiveValue primitiveValue) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Write primitive value - implementation of the actual functionality. + /// + /// Primitive value to write. + private void WritePrimitiveValueImplementation(ODataPrimitiveValue primitiveValue) + { + this.InterceptException( + (thisParam, primitiveValueParam) => + { + thisParam.EnterScope(WriterState.Primitive, primitiveValueParam); + if (!(thisParam.CurrentResourceSetValidator == null) && primitiveValueParam != null) + { + Debug.Assert(primitiveValueParam.Value != null, "PrimitiveValue.Value should never be null!"); + IEdmType itemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primitiveValueParam.Value.GetType()).Definition; + thisParam.CurrentResourceSetValidator.ValidateResource(itemType); + } + + thisParam.WritePrimitiveValue(primitiveValueParam); + thisParam.WriteEnd(); + }, primitiveValue); + } + + /// + /// Write primitive value asynchronously - implementation of the actual functionality. + /// + /// Primitive value to write. + /// The task. + private async Task WritePrimitiveValueImplementationAsync(ODataPrimitiveValue primitiveValue) + { + EnterScope(WriterState.Primitive, primitiveValue); + + await InterceptExceptionAsync( + async (thisParam, primiteValueParam) => + { + if (!(CurrentResourceSetValidator == null) && primiteValueParam != null) + { + Debug.Assert(primiteValueParam.Value != null, "PrimitiveValue.Value should never be null!"); + IEdmType itemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primiteValueParam.Value.GetType()).Definition; + CurrentResourceSetValidator.ValidateResource(itemType); + } + + await thisParam.WritePrimitiveValueAsync(primiteValueParam) + .ConfigureAwait(false); + await thisParam.WriteEndAsync() + .ConfigureAwait(false); + }, primitiveValue).ConfigureAwait(false); + } + + /// + /// Verifies that calling CreateWriteStream is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanCreateWriteStream(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Create a write stream - implementation of the actual functionality. + /// + /// A stream for writing the binary value. + private Stream CreateWriteStreamImplementation() + { + this.EnterScope(WriterState.Stream, null); + return new ODataNotificationStream(this.StartBinaryStream(), this); + } + + /// + /// Verifies that calling CreateTextWriter is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanCreateTextWriter(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Create a text writer - implementation of the actual functionality. + /// + /// A TextWriter for writing the string value. + private TextWriter CreateTextWriterImplementation() + { + this.EnterScope(WriterState.String, null); + return new ODataNotificationWriter(this.StartTextWriter(), this); + } + + /// + /// Verify that calling WriteEnd is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanWriteEnd(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Finish writing a resourceSet/resource/nested resource info. + /// + private void WriteEndImplementation() + { + this.InterceptException( + (thisParam) => + { + Scope currentScope = thisParam.CurrentScope; + + switch (currentScope.State) + { + case WriterState.Resource: + if (!thisParam.SkipWriting) + { + ODataResource resource = (ODataResource)currentScope.Item; + + thisParam.EndResource(resource); + thisParam.DecreaseResourceDepth(); + } + + break; + case WriterState.DeletedResource: + if (!thisParam.SkipWriting) + { + ODataDeletedResource resource = (ODataDeletedResource)currentScope.Item; + + thisParam.EndDeletedResource(resource); + thisParam.DecreaseResourceDepth(); + } + + break; + case WriterState.ResourceSet: + if (!thisParam.SkipWriting) + { + ODataResourceSet resourceSet = (ODataResourceSet)currentScope.Item; + WriterValidationUtils.ValidateResourceSetAtEnd(resourceSet, !thisParam.outputContext.WritingResponse); + thisParam.EndResourceSet(resourceSet); + } + + break; + case WriterState.DeltaLink: + case WriterState.DeltaDeletedLink: + break; + case WriterState.DeltaResourceSet: + if (!thisParam.SkipWriting) + { + ODataDeltaResourceSet deltaResourceSet = (ODataDeltaResourceSet)currentScope.Item; + WriterValidationUtils.ValidateDeltaResourceSetAtEnd(deltaResourceSet, !thisParam.outputContext.WritingResponse); + thisParam.EndDeltaResourceSet(deltaResourceSet); + } + + break; + case WriterState.NestedResourceInfo: + if (!thisParam.outputContext.WritingResponse) + { + throw new ODataException(Strings.ODataWriterCore_DeferredLinkInRequest); + } + + if (!thisParam.SkipWriting) + { + ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item; + thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(link); + thisParam.WriteDeferredNestedResourceInfo(link); + + thisParam.MarkNestedResourceInfoAsProcessed(link); + } + + break; + case WriterState.NestedResourceInfoWithContent: + if (!thisParam.SkipWriting) + { + ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item; + thisParam.EndNestedResourceInfoWithContent(link); + + thisParam.MarkNestedResourceInfoAsProcessed(link); + } + + break; + case WriterState.Property: + { + ODataPropertyInfo property = (ODataPropertyInfo)currentScope.Item; + thisParam.EndProperty(property); + } + + break; + case WriterState.Primitive: + // WriteEnd for WriterState.Primitive is a no-op; just leave scope + break; + case WriterState.Stream: + case WriterState.String: + throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed); + case WriterState.Start: // fall through + case WriterState.Completed: // fall through + case WriterState.Error: // fall through + throw new ODataException(Strings.ODataWriterCore_WriteEndCalledInInvalidState(currentScope.State.ToString())); + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_WriteEnd_UnreachableCodePath)); + } + + thisParam.LeaveScope(); + }); + } + + /// + /// Marks the navigation currently being written as processed in the parent entity's metadata builder. + /// This is needed so that at the end of writing the resource we can query for all the unwritten navigation properties + /// defined on the entity type and write out their metadata in fullmetadata mode. + /// + /// The nested resource info being written. + private void MarkNestedResourceInfoAsProcessed(ODataNestedResourceInfo link) + { + Debug.Assert( + this.CurrentScope.State == WriterState.NestedResourceInfo || this.CurrentScope.State == WriterState.NestedResourceInfoWithContent, + "This method should only be called when we're writing a nested resource info."); + + ODataResourceBase parent = (ODataResourceBase)this.scopeStack.Parent.Item; + Debug.Assert(parent.MetadataBuilder != null, "parent.MetadataBuilder != null"); + parent.MetadataBuilder.MarkNestedResourceInfoProcessed(link.Name); + } + + /// + /// Verifies that calling WriteEntityReferenceLink is valid. + /// + /// The entity reference link to write. + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanWriteEntityReferenceLink(ODataEntityReferenceLink entityReferenceLink, bool synchronousCall) + { + ExceptionUtils.CheckArgumentNotNull(entityReferenceLink, "entityReferenceLink"); + + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that calling Write(Deleted)DeltaLink is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Delta link to write. + private void VerifyCanWriteLink(bool synchronousCall, ODataDeltaLinkBase deltaLink) + { + this.VerifyWritingDelta(); + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + + ExceptionUtils.CheckArgumentNotNull(deltaLink, "deltaLink"); + } + + /// + /// Write an entity reference link. + /// + /// The entity reference link to write. + private void WriteEntityReferenceLinkImplementation(ODataEntityReferenceLink entityReferenceLink) + { + Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null"); + + this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.EntityReferenceLink, null); + Debug.Assert( + this.CurrentScope.Item is ODataNestedResourceInfo || this.ParentNestedResourceInfoScope.Item is ODataNestedResourceInfo, + "The CheckForNestedResourceInfoWithContent should have verified that entity reference link can only be written inside a nested resource info."); + + if (!this.SkipWriting) + { + this.InterceptException( + (thisParam, entityReferenceLinkParam) => + { + WriterValidationUtils.ValidateEntityReferenceLink(entityReferenceLinkParam); + + ODataNestedResourceInfo nestedInfo = thisParam.CurrentScope.Item as ODataNestedResourceInfo; + if (nestedInfo == null) + { + NestedResourceInfoScope nestedResourceInfoScope = thisParam.ParentNestedResourceInfoScope; + Debug.Assert(nestedResourceInfoScope != null); + nestedInfo = (ODataNestedResourceInfo)nestedResourceInfoScope.Item; + } + + thisParam.WriteEntityReferenceInNavigationLinkContent(nestedInfo, entityReferenceLinkParam); + }, entityReferenceLink); + } + } + + /// + /// Verifies that calling Flush is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanFlush(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that a call is allowed to the writer. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCallAllowed(bool synchronousCall) + { + if (synchronousCall) + { + if (!this.outputContext.Synchronous) + { + throw new ODataException(Strings.ODataWriterCore_SyncCallOnAsyncWriter); + } + } + else + { + if (this.outputContext.Synchronous) + { + throw new ODataException(Strings.ODataWriterCore_AsyncCallOnSyncWriter); + } + } + } + + /// + /// Verifies that the writer is writing within a delta resource set. + /// + private void VerifyWritingDelta() + { + if (!this.CurrentScope.EnableDelta) + { + throw new ODataException(Strings.ODataWriterCore_CannotWriteDeltaWithResourceSetWriter); + } + } + + /// + /// Enters the 'Error' state and then throws an ODataException with the specified error message. + /// + /// The error message for the exception. + /// The OData item to associate with the 'Error' state. + private void ThrowODataException(string errorMessage, ODataItem item) + { + this.EnterScope(WriterState.Error, item); + throw new ODataException(errorMessage); + } + + /// + /// Checks whether we are currently writing the first top-level element; if so call StartPayload + /// + private void StartPayloadInStartState() + { + if (this.State == WriterState.Start) + { + this.InterceptException((thisParam) => thisParam.StartPayload()); + } + } + + /// + /// Checks whether we are currently writing a nested resource info and switches to NestedResourceInfoWithContent state if we do. + /// + /// + /// What kind of payload kind is being written as the content of a nested resource info. + /// Only Resource Set, Resource or EntityReferenceLink are allowed. + /// + /// The ODataResource or ODataResourceSet to write, or null for ODataEntityReferenceLink. + private void CheckForNestedResourceInfoWithContent(ODataPayloadKind contentPayloadKind, ODataItem contentPayload) + { + Debug.Assert( + contentPayloadKind == ODataPayloadKind.ResourceSet || contentPayloadKind == ODataPayloadKind.Resource || contentPayloadKind == ODataPayloadKind.EntityReferenceLink, + "Only ResourceSet, Resource or EntityReferenceLink can be specified as a payload kind for a nested resource info content."); + + Scope currentScope = this.CurrentScope; + if (currentScope.State == WriterState.NestedResourceInfo || currentScope.State == WriterState.NestedResourceInfoWithContent) + { + ODataNestedResourceInfo currentNestedResourceInfo = (ODataNestedResourceInfo)currentScope.Item; + this.InterceptException( + (thisParam, currentNestedResourceInfoParam, contentPayloadKindParam) => + { + if (thisParam.ParentResourceType != null) + { + IEdmStructuralProperty structuralProperty = thisParam.ParentResourceType.FindProperty(currentNestedResourceInfoParam.Name) as IEdmStructuralProperty; + if (structuralProperty != null) + { + thisParam.CurrentScope.ItemType = structuralProperty.Type.Definition.AsElementType(); + IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource; + + thisParam.CurrentScope.NavigationSource = parentNavigationSource; + } + else + { + IEdmNavigationProperty navigationProperty = + thisParam.WriterValidator.ValidateNestedResourceInfo(currentNestedResourceInfoParam, thisParam.ParentResourceType, contentPayloadKindParam); + if (navigationProperty != null) + { + thisParam.CurrentScope.ResourceType = navigationProperty.ToEntityType(); + IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource; + + if (thisParam.CurrentScope.NavigationSource == null) + { + IEdmPathExpression bindingPath; + thisParam.CurrentScope.NavigationSource = parentNavigationSource == null ? + null : + parentNavigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, thisParam.CurrentScope.ODataUri.Path.Segments, out bindingPath); + } + } + } + } + }, currentNestedResourceInfo, contentPayloadKind); + + if (currentScope.State == WriterState.NestedResourceInfoWithContent) + { + // If we are already in the NestedResourceInfoWithContent state, it means the caller is trying to write two items + // into the nested resource info content. This is only allowed for collection navigation property in request/response. + if (currentNestedResourceInfo.IsCollection != true) + { + this.ThrowODataException(Strings.ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent, currentNestedResourceInfo); + } + + // Note that we don't invoke duplicate property checker in this case as it's not necessary. + // What happens inside the nested resource info was already validated by the condition above. + // For collection in request we allow any combination anyway. + // For everything else we only allow a single item in the content and thus we will fail above. + } + else + { + // we are writing a nested resource info with content; change the state + this.PromoteNestedResourceInfoScope(contentPayload); + + if (!this.SkipWriting) + { + this.InterceptException( + (thisParam, currentNestedResourceInfoParam) => + { + if (!(currentNestedResourceInfoParam.SerializationInfo != null && currentNestedResourceInfoParam.SerializationInfo.IsComplex) + && (thisParam.CurrentScope.ItemType == null || thisParam.CurrentScope.ItemType.IsEntityOrEntityCollectionType())) + { + thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(currentNestedResourceInfoParam); + thisParam.StartNestedResourceInfoWithContent(currentNestedResourceInfoParam); + } + }, currentNestedResourceInfo); + } + } + } + else + { + if (contentPayloadKind == ODataPayloadKind.EntityReferenceLink) + { + Scope parenScope = this.ParentNestedResourceInfoScope; + Debug.Assert(parenScope != null); + if (parenScope.State != WriterState.NestedResourceInfo && parenScope.State != WriterState.NestedResourceInfoWithContent) + { + this.ThrowODataException(Strings.ODataWriterCore_EntityReferenceLinkWithoutNavigationLink, null); + } + } + } + } + + /// + /// Verifies that the (deleted) resource has the correct type for the (delta) resource set. + /// + /// The resource to be validated. + /// The scope for the resource to be validated. + private void ValidateResourceForResourceSet(ODataResourceBase resource, ResourceBaseScope resourceScope) + { + IEdmStructuredType resourceType = GetResourceType(resource); + NestedResourceInfoScope parentNestedResourceInfoScope = this.ParentNestedResourceInfoScope; + if (parentNestedResourceInfoScope != null) + { + // Validate the consistency of resource types in the nested resourceSet/resource + this.WriterValidator.ValidateResourceInNestedResourceInfo(resourceType, parentNestedResourceInfoScope.ResourceType); + resourceScope.ResourceTypeFromMetadata = parentNestedResourceInfoScope.ResourceType; + + this.WriterValidator.ValidateDerivedTypeConstraint(resourceType, resourceScope.ResourceTypeFromMetadata, + parentNestedResourceInfoScope.DerivedTypeConstraints, "property", ((ODataNestedResourceInfo)parentNestedResourceInfoScope.Item).Name); + } + else + { + resourceScope.ResourceTypeFromMetadata = this.ParentScope.ResourceType; + if (this.CurrentResourceSetValidator != null) + { + if (this.ParentScope.State == WriterState.DeltaResourceSet + && this.currentResourceDepth <= 1 + && resourceScope.NavigationSource != null) + { + // if the (deleted) resource is in the top level of a delta resource set, it doesn't + // need to match the delta resource set, but must match the navigation source resolved for + // the current scope + if (!resourceScope.NavigationSource.EntityType().IsAssignableFrom(resourceType)) + { + throw new ODataException(Strings.ResourceSetWithoutExpectedTypeValidator_IncompatibleTypes(resourceType.FullTypeName(), resourceScope.NavigationSource.EntityType())); + } + + resourceScope.ResourceTypeFromMetadata = resourceScope.NavigationSource.EntityType(); + } + else + { + // Validate the consistency of resource types + this.CurrentResourceSetValidator.ValidateResource(resourceType); + } + } + + if (this.ParentScope.NavigationSource != null) + { + this.WriterValidator.ValidateDerivedTypeConstraint(resourceType, resourceScope.ResourceTypeFromMetadata, + this.ParentScope.DerivedTypeConstraints, "navigation source", this.ParentScope.NavigationSource.Name); + } + } + + resourceScope.ResourceType = resourceType; + + // If writing in a delta resource set, the entity must have all key properties or the id set + if (this.ParentScope.State == WriterState.DeltaResourceSet) + { + IEdmEntityType entityType = resourceType as IEdmEntityType; + if (resource.Id == null && + entityType != null && + (this.outputContext.WritingResponse || resource is ODataDeletedResource) && + !HasKeyProperties(entityType, resource.Properties)) + { + throw new ODataException(Strings.ODataWriterCore_DeltaResourceWithoutIdOrKeyProperties); + } + } + } + + /// + /// Determines whether a collection contains all key properties for a particular entity type. + /// + /// The entity type. + /// The set of properties. + /// True if the set of properties include all key properties for the entity type; otherwise false. + private static bool HasKeyProperties(IEdmEntityType entityType, IEnumerable properties) + { + Debug.Assert(entityType != null, "entityType null"); + if (properties == null) + { + return false; + } + + return entityType.Key().All(keyProp => properties.Select(p => p.Name).Contains(keyProp.Name)); + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the writer into + /// state Error and then rethrow the exception. + /// + /// The action to execute. + /// + /// Make sure to only use anonymous functions that don't capture state from the enclosing context, + /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method. + /// + private void InterceptException(Action action) + { + try + { + action(this); + } + catch + { + if (!IsErrorState(this.State)) + { + this.EnterScope(WriterState.Error, this.CurrentScope.Item); + } + + throw; + } + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the writer into + /// state Error and then rethrow the exception. + /// + /// The action argument type. + /// The action to execute. + /// The argument value provided to the action. + /// + /// Make sure to only use anonymous functions that don't capture state from the enclosing context, + /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method. + /// + private void InterceptException(Action action, TArg0 arg0) + { + try + { + action(this, arg0); + } + catch + { + if (!IsErrorState(this.State)) + { + this.EnterScope(WriterState.Error, this.CurrentScope.Item); + } + + throw; + } + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the writer into + /// state Error and then rethrow the exception. + /// + /// The delegate first argument type. + /// The delegate second argument type. + /// The action to execute. + /// The argument value provided to the action. + /// The argument value provided to the action. + /// + /// Make sure to only use anonymous functions that don't capture state from the enclosing context, + /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method. + /// + private void InterceptException(Action action, TArg0 arg0, TArg1 arg1) + { + try + { + action(this, arg0, arg1); + } + catch + { + if (!IsErrorState(this.State)) + { + this.EnterScope(WriterState.Error, this.CurrentScope.Item); + } + + throw; + } + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the writer into + /// state Error and then rethrow the exception. + /// + /// The action to execute. + /// The task. + /// + /// Make sure to only use anonymous functions that don't capture state from the enclosing context, + /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method. + /// + private async Task InterceptExceptionAsync(Func action) + { + try + { + await action(this).ConfigureAwait(false); + } + catch + { + if (!IsErrorState(this.State)) + { + this.EnterScope(WriterState.Error, this.CurrentScope.Item); + } + + throw; + } + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the writer into + /// state Error and then rethrow the exception. + /// + /// The action argument type. + /// The action to execute. + /// The argument value provided to the action. + /// The task. + /// + /// Make sure to only use anonymous functions that don't capture state from the enclosing context, + /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method. + /// + private async Task InterceptExceptionAsync(Func action, TArg0 arg0) + { + try + { + await action(this, arg0).ConfigureAwait(false); + } + catch + { + if (!IsErrorState(this.State)) + { + this.EnterScope(WriterState.Error, this.CurrentScope.Item); + } + + throw; + } + } + + /// + /// Increments the nested resource count by one and fails if the new value exceeds the maximum nested resource depth limit. + /// + private void IncreaseResourceDepth() + { + this.currentResourceDepth++; + + if (this.currentResourceDepth > this.outputContext.MessageWriterSettings.MessageQuotas.MaxNestingDepth) + { + this.ThrowODataException(Strings.ValidationUtils_MaxDepthOfNestedEntriesExceeded(this.outputContext.MessageWriterSettings.MessageQuotas.MaxNestingDepth), null); + } + } + + /// + /// Decrements the nested resource count by one. + /// + private void DecreaseResourceDepth() + { + Debug.Assert(this.currentResourceDepth > 0, "Resource depth should never become negative."); + + this.currentResourceDepth--; + } + + + /// + /// Notifies the implementer of the interface of relevant state changes in the writer. + /// + /// The new writer state. + private void NotifyListener(WriterState newState) + { + if (this.listener != null) + { + if (IsErrorState(newState)) + { + this.listener.OnException(); + } + else if (newState == WriterState.Completed) + { + this.listener.OnCompleted(); + } + } + } + + /// + /// Enter a new writer scope; verifies that the transition from the current state into new state is valid + /// and attaches the item to the new scope. + /// + /// The writer state to transition into. + /// The item to associate with the new scope. + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug only cast.")] + private void EnterScope(WriterState newState, ODataItem item) + { + this.InterceptException((thisParam, newStateParam) => thisParam.ValidateTransition(newStateParam), newState); + + // If the parent scope was marked for skipping content, the new child scope should be as well. + bool skipWriting = this.SkipWriting; + + Scope currentScope = this.CurrentScope; + + IEdmNavigationSource navigationSource = null; + IEdmType itemType = null; + SelectedPropertiesNode selectedProperties = currentScope.SelectedProperties; + ODataUri odataUri = currentScope.ODataUri.Clone(); + if (odataUri.Path == null) + { + odataUri.Path = new ODataPath(); + } + + IEnumerable derivedTypeConstraints = null; + + WriterState currentState = currentScope.State; + + if (newState == WriterState.Resource || newState == WriterState.ResourceSet || newState == WriterState.Primitive || newState == WriterState.DeltaResourceSet || newState == WriterState.DeletedResource) + { + // if we're in a DeltaResourceSet and writing a resource or deleted resource then the parent may not be the navigation source + ODataResourceBase resource = item as ODataResourceBase; + if (resource != null) + { + IEdmModel model = this.outputContext.Model; + if (model != null && model.IsUserModel()) + { + try + { + string typeNameFromResource = resource.TypeName; + if (!String.IsNullOrEmpty(typeNameFromResource)) + { + // try resolving type from resource TypeName + itemType = TypeNameOracle.ResolveAndValidateTypeName( + model, + typeNameFromResource, + EdmTypeKind.None, + /* expectStructuredType */ true, + this.outputContext.WriterValidator); + } + + // Try resolving navigation source from serialization info. + ODataResourceSerializationInfo serializationInfo = resource.SerializationInfo; + if (serializationInfo != null) + { + if (serializationInfo.NavigationSourceName != null) + { + ODataUriParser uriParser = new ODataUriParser(model, new Uri(serializationInfo.NavigationSourceName, UriKind.Relative), this.outputContext.Container); + odataUri = uriParser.ParseUri(); + navigationSource = odataUri.Path.NavigationSource(); + itemType = itemType ?? navigationSource.EntityType(); + } + + if (typeNameFromResource == null) + { + // Try resolving entity type from SerializationInfo + if (!string.IsNullOrEmpty(serializationInfo.ExpectedTypeName)) + { + itemType = TypeNameOracle.ResolveAndValidateTypeName( + model, + serializationInfo.ExpectedTypeName, + EdmTypeKind.None, + /* expectStructuredType */ true, + this.outputContext.WriterValidator); + } + else if (!string.IsNullOrEmpty(serializationInfo.NavigationSourceEntityTypeName)) + { + itemType = TypeNameOracle.ResolveAndValidateTypeName( + model, + serializationInfo.NavigationSourceEntityTypeName, + EdmTypeKind.Entity, + /* expectStructuredType */ true, + this.outputContext.WriterValidator); + } + } + } + } + catch (ODataException) + { + // SerializationInfo doesn't match model. + // This should be an error but, for legacy reasons, we ignore this. + } + } + } + + if (navigationSource == null) + { + derivedTypeConstraints = currentScope.DerivedTypeConstraints; + } + else + { + derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationSource); + } + + navigationSource = navigationSource ?? currentScope.NavigationSource; + itemType = itemType ?? currentScope.ItemType; + + // This is to resolve the item type for a resource set for an undeclared nested resource info. + if (itemType == null + && (currentState == WriterState.Start || currentState == WriterState.NestedResourceInfo || currentState == WriterState.NestedResourceInfoWithContent) + && (newState == WriterState.ResourceSet || newState == WriterState.DeltaResourceSet)) + { + ODataResourceSetBase resourceSet = item as ODataResourceSetBase; + if (resourceSet != null && resourceSet.TypeName != null && this.outputContext.Model.IsUserModel()) + { + IEdmCollectionType collectionType = TypeNameOracle.ResolveAndValidateTypeName( + this.outputContext.Model, + resourceSet.TypeName, + EdmTypeKind.Collection, + false, + this.outputContext.WriterValidator) as IEdmCollectionType; + + if (collectionType != null) + { + itemType = collectionType.ElementType.Definition; + } + } + } + } + + // When writing a nested resource info, check if the link is being projected. + // If we are projecting properties, but the nav. link is not projected mark it to skip its content. + if ((currentState == WriterState.Resource || currentState == WriterState.DeletedResource) && newState == WriterState.NestedResourceInfo) + { + Debug.Assert(currentScope.Item is ODataResourceBase, "If the current state is Resource the current Item must be resource as well (and not null either)."); + Debug.Assert(item is ODataNestedResourceInfo, "If the new state is NestedResourceInfo the new item must be a nested resource info as well (and not null either)."); + ODataNestedResourceInfo nestedResourceInfo = (ODataNestedResourceInfo)item; + + if (!skipWriting) + { + selectedProperties = currentScope.SelectedProperties.GetSelectedPropertiesForNavigationProperty(currentScope.ResourceType, nestedResourceInfo.Name); + + ODataPath odataPath = odataUri.Path; + IEdmStructuredType currentResourceType = currentScope.ResourceType; + + ResourceBaseScope resourceScope = currentScope as ResourceBaseScope; + TypeSegment resourceTypeCast = null; + if (resourceScope.ResourceTypeFromMetadata != currentResourceType) + { + resourceTypeCast = new TypeSegment(currentResourceType, null); + } + + IEdmStructuralProperty structuredProperty = this.WriterValidator.ValidatePropertyDefined( + nestedResourceInfo.Name, currentResourceType) + as IEdmStructuralProperty; + + // Handle primitive or complex type property. + if (structuredProperty != null) + { + odataPath = AppendEntitySetKeySegment(odataPath, false); + itemType = structuredProperty.Type == null ? null : structuredProperty.Type.Definition.AsElementType(); + navigationSource = null; + + if (resourceTypeCast != null) + { + odataPath = odataPath.AddSegment(resourceTypeCast); + } + + odataPath = odataPath.AddPropertySegment(structuredProperty); + + derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(structuredProperty); + } + else + { + IEdmNavigationProperty navigationProperty = this.WriterValidator.ValidateNestedResourceInfo(nestedResourceInfo, currentResourceType, /*payloadKind*/null); + if (navigationProperty != null) + { + derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationProperty); + + itemType = navigationProperty.ToEntityType(); + if (!nestedResourceInfo.IsCollection.HasValue) + { + nestedResourceInfo.IsCollection = navigationProperty.Type.IsEntityCollectionType(); + } + + IEdmNavigationSource currentNavigationSource = currentScope.NavigationSource; + IEdmPathExpression bindingPath; + + if (resourceTypeCast != null) + { + odataPath = odataPath.AddSegment(resourceTypeCast); + } + + navigationSource = currentNavigationSource == null + ? null + : currentNavigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, odataPath.Segments, out bindingPath); + + SelectExpandClause clause = odataUri.SelectAndExpand; + TypeSegment typeCastFromExpand = null; + if (clause != null) + { + SelectExpandClause subClause; + clause.GetSubSelectExpandClause(nestedResourceInfo.Name, out subClause, out typeCastFromExpand); + odataUri.SelectAndExpand = subClause; + } + + switch (navigationSource.NavigationSourceKind()) + { + case EdmNavigationSourceKind.ContainedEntitySet: + // Containment cannot be written alone without odata uri. + if (!odataPath.Any()) + { + throw new ODataException(Strings.ODataWriterCore_PathInODataUriMustBeSetWhenWritingContainedElement); + } + + odataPath = AppendEntitySetKeySegment(odataPath, true); + + if (odataPath != null && typeCastFromExpand != null) + { + odataPath = odataPath.AddSegment(typeCastFromExpand); + } + + Debug.Assert(navigationSource is IEdmContainedEntitySet, "If the NavigationSourceKind is ContainedEntitySet, the navigationSource must be IEdmContainedEntitySet."); + IEdmContainedEntitySet containedEntitySet = (IEdmContainedEntitySet)navigationSource; + odataPath = odataPath.AddNavigationPropertySegment(containedEntitySet.NavigationProperty, containedEntitySet); + break; + case EdmNavigationSourceKind.EntitySet: + odataPath = new ODataPath(new EntitySetSegment(navigationSource as IEdmEntitySet)); + break; + case EdmNavigationSourceKind.Singleton: + odataPath = new ODataPath(new SingletonSegment(navigationSource as IEdmSingleton)); + break; + default: + odataPath = null; + break; + } + } + } + + odataUri.Path = odataPath; + } + } + else if ((currentState == WriterState.ResourceSet || currentState == WriterState.DeltaResourceSet) && (newState == WriterState.Resource || newState == WriterState.Primitive || newState == WriterState.ResourceSet || newState == WriterState.DeletedResource)) + { + // When writing a new resource to a resourceSet, increment the count of entries on that resourceSet. + if (currentState == WriterState.ResourceSet || currentState == WriterState.DeltaResourceSet) + { + ((ResourceSetBaseScope)currentScope).ResourceCount++; + } + } + + if (navigationSource == null) + { + navigationSource = this.CurrentScope.NavigationSource ?? odataUri.Path.TargetNavigationSource(); + } + + this.PushScope(newState, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, derivedTypeConstraints); + + this.NotifyListener(newState); + } + + /// + /// Attempt to append key segment to ODataPath. + /// + /// The ODataPath to be evaluated. + /// Whether throw if fails to append key segment. + /// The new odata path. + private ODataPath AppendEntitySetKeySegment(ODataPath odataPath, bool throwIfFail) + { + ODataPath path = odataPath; + + if (EdmExtensionMethods.HasKey(this.CurrentScope.NavigationSource, this.CurrentScope.ResourceType)) + { + IEdmEntityType currentEntityType = this.CurrentScope.ResourceType as IEdmEntityType; + ODataResourceBase resource = this.CurrentScope.Item as ODataResourceBase; + Debug.Assert(resource != null, + "If the current state is Resource the current item must be an ODataResource as well (and not null either)."); + + ODataResourceSerializationInfo serializationInfo = this.GetResourceSerializationInfo(resource); + + KeyValuePair[] keys = ODataResourceMetadataContext.GetKeyProperties(resource, + serializationInfo, currentEntityType, throwIfFail); + + path = path.AddKeySegment(keys, currentEntityType, this.CurrentScope.NavigationSource); + } + + return path; + } + + /// + /// Leave the current writer scope and return to the previous scope. + /// When reaching the top-level replace the 'Started' scope with a 'Completed' scope. + /// + /// Note that this method is never called once an error has been written or a fatal exception has been thrown. + private void LeaveScope() + { + Debug.Assert(this.State != WriterState.Error, "this.State != WriterState.Error"); + + this.scopeStack.Pop(); + + // if we are back at the root replace the 'Start' state with the 'Completed' state + if (this.scopeStack.Count == 1) + { + Scope startScope = this.scopeStack.Pop(); + Debug.Assert(startScope.State == WriterState.Start, "startScope.State == WriterState.Start"); + this.PushScope(WriterState.Completed, /*item*/null, startScope.NavigationSource, startScope.ResourceType, /*skipWriting*/false, startScope.SelectedProperties, startScope.ODataUri, null); + this.InterceptException((thisParam) => thisParam.EndPayload()); + this.NotifyListener(WriterState.Completed); + } + } + + /// + /// Promotes the current nested resource info scope to a nested resource info scope with content. + /// + /// The nested content to write. May be of either ODataResource or ODataResourceSet type. + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Second cast only in debug.")] + private void PromoteNestedResourceInfoScope(ODataItem content) + { + Debug.Assert( + this.State == WriterState.NestedResourceInfo, + "Only a NestedResourceInfo state can be promoted right now. If this changes please review the scope replacement code below."); + Debug.Assert( + this.CurrentScope.Item != null && this.CurrentScope.Item is ODataNestedResourceInfo, + "Item must be a non-null nested resource info."); + Debug.Assert(content == null || content is ODataResourceBase || content is ODataResourceSetBase); + + this.ValidateTransition(WriterState.NestedResourceInfoWithContent); + NestedResourceInfoScope previousScope = (NestedResourceInfoScope)this.scopeStack.Pop(); + NestedResourceInfoScope newScope = previousScope.Clone(WriterState.NestedResourceInfoWithContent); + + this.scopeStack.Push(newScope); + if (newScope.ItemType == null && content != null && !SkipWriting && !(content is ODataPrimitiveValue)) + { + ODataPrimitiveValue primitiveValue = content as ODataPrimitiveValue; + if (primitiveValue != null) + { + newScope.ItemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primitiveValue.GetType()).Definition; + } + else + { + ODataResourceBase resource = content as ODataResourceBase; + newScope.ResourceType = resource != null + ? GetResourceType(resource) + : GetResourceSetType(content as ODataResourceSetBase); + } + } + } + + /// + /// Verify that the transition from the current state into new state is valid . + /// + /// The new writer state to transition into. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "All the transition checks are encapsulated in this method.")] + private void ValidateTransition(WriterState newState) + { + if (!IsErrorState(this.State) && IsErrorState(newState)) + { + // we can always transition into an error state if we are not already in an error state + return; + } + + switch (this.State) + { + case WriterState.Start: + if (newState != WriterState.ResourceSet && newState != WriterState.Resource && newState != WriterState.DeltaResourceSet && newState != WriterState.DeletedResource) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromStart(this.State.ToString(), newState.ToString())); + } + + if ((newState == WriterState.ResourceSet || newState == WriterState.DeltaResourceSet) && !this.writingResourceSet) + { + throw new ODataException(Strings.ODataWriterCore_CannotWriteTopLevelResourceSetWithResourceWriter); + } + + if (newState == WriterState.Resource && this.writingResourceSet) + { + throw new ODataException(Strings.ODataWriterCore_CannotWriteTopLevelResourceWithResourceSetWriter); + } + + break; + case WriterState.DeletedResource: + case WriterState.Resource: + { + if (this.CurrentScope.Item == null) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromNullResource(this.State.ToString(), newState.ToString())); + } + + if (newState != WriterState.NestedResourceInfo && newState != WriterState.Property) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResource(this.State.ToString(), newState.ToString())); + } + + // TODO: The conditional expressions in the 2 `if` blocks below are adequately covered by the `if` block above? + if (newState == WriterState.DeletedResource && this.ParentScope.State != WriterState.DeltaResourceSet) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString())); + } + + if (this.State == WriterState.DeletedResource && this.Version < ODataVersion.V401 && newState == WriterState.NestedResourceInfo) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFrom40DeletedResource(this.State.ToString(), newState.ToString())); + } + } + + break; + case WriterState.ResourceSet: + // Within a typed resource set we can only write a resource. + // Within an untyped resource set we can also write a primitive value or nested resource set. + if (newState != WriterState.Resource && + (this.CurrentScope.ResourceType != null && + (this.CurrentScope.ResourceType.TypeKind != EdmTypeKind.Untyped || + (newState != WriterState.Primitive && newState != WriterState.Stream && newState != WriterState.String && newState != WriterState.ResourceSet)))) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString())); + } + + break; + case WriterState.DeltaResourceSet: + if (newState != WriterState.Resource && + newState != WriterState.DeletedResource && + !(this.ScopeLevel < 3 && (newState == WriterState.DeltaDeletedLink || newState == WriterState.DeltaLink))) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString())); + } + + break; + case WriterState.NestedResourceInfo: + if (newState != WriterState.NestedResourceInfoWithContent) + { + throw new ODataException(Strings.ODataWriterCore_InvalidStateTransition(this.State.ToString(), newState.ToString())); + } + + break; + case WriterState.NestedResourceInfoWithContent: + if (newState != WriterState.ResourceSet && newState != WriterState.Resource && newState != WriterState.Primitive && (this.Version < ODataVersion.V401 || (newState != WriterState.DeltaResourceSet && newState != WriterState.DeletedResource))) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromExpandedLink(this.State.ToString(), newState.ToString())); + } + + break; + case WriterState.Property: + PropertyInfoScope propertyScope = this.CurrentScope as PropertyInfoScope; + Debug.Assert(propertyScope != null, "Scope in WriterState.Property is not PropertyInfoScope"); + if (propertyScope.ValueWritten) + { + // we've already written the value for this property + ODataPropertyInfo propertyInfo = propertyScope.Item as ODataPropertyInfo; + Debug.Assert(propertyInfo != null, "Item in PropertyInfoScope is not ODataPropertyInfo"); + throw new ODataException(Strings.ODataWriterCore_PropertyValueAlreadyWritten(propertyInfo.Name)); + } + + if (newState == WriterState.Stream || newState == WriterState.String || newState == WriterState.Primitive) + { + propertyScope.ValueWritten = true; + } + else + { + throw new ODataException(Strings.ODataWriterCore_InvalidStateTransition(this.State.ToString(), newState.ToString())); + } + + break; + case WriterState.Stream: + case WriterState.String: + throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed); + case WriterState.Completed: + // we should never see a state transition when in state 'Completed' + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), newState.ToString())); + case WriterState.Error: + if (newState != WriterState.Error) + { + // No more state transitions once we are in error state except for the fatal error + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromError(this.State.ToString(), newState.ToString())); + } + + break; + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_ValidateTransition_UnreachableCodePath)); + } + } + + /// + /// Create a new writer scope. + /// + /// The writer state of the scope to create. + /// The item attached to the scope to create. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the navigationSource base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The OdataUri info of this scope. + /// The derived type constraints. + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug.Assert check only.")] + private void PushScope(WriterState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, + IEnumerable derivedTypeConstraints) + { + IEdmStructuredType resourceType = itemType as IEdmStructuredType; + + Debug.Assert( + state == WriterState.Error || + state == WriterState.Resource && (item == null || item is ODataResource) || + state == WriterState.DeletedResource && (item == null || item is ODataDeletedResource) || + state == WriterState.DeltaLink && (item == null || item is ODataDeltaLink) || + state == WriterState.DeltaDeletedLink && (item == null || item is ODataDeltaDeletedLink) || + state == WriterState.ResourceSet && item is ODataResourceSet || + state == WriterState.DeltaResourceSet && item is ODataDeltaResourceSet || + state == WriterState.Primitive && (item == null || item is ODataPrimitiveValue) || + state == WriterState.Property && (item is ODataPropertyInfo) || + state == WriterState.NestedResourceInfo && item is ODataNestedResourceInfo || + state == WriterState.NestedResourceInfoWithContent && item is ODataNestedResourceInfo || + state == WriterState.Stream && item == null || + state == WriterState.String && item == null || + state == WriterState.Start && item == null || + state == WriterState.Completed && item == null, + "Writer state and associated item do not match."); + + bool isUndeclaredResourceOrResourceSet = false; + if ((state == WriterState.Resource || state == WriterState.ResourceSet) + && (this.CurrentScope.State == WriterState.NestedResourceInfo || this.CurrentScope.State == WriterState.NestedResourceInfoWithContent)) + { + isUndeclaredResourceOrResourceSet = this.IsUndeclared(this.CurrentScope.Item as ODataNestedResourceInfo); + } + + Scope scope; + switch (state) + { + case WriterState.Resource: + scope = this.CreateResourceScope((ODataResource)item, navigationSource, resourceType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet); + break; + case WriterState.DeletedResource: + scope = this.CreateDeletedResourceScope((ODataDeletedResource)item, navigationSource, (IEdmEntityType)itemType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet); + break; + case WriterState.DeltaLink: + case WriterState.DeltaDeletedLink: + scope = this.CreateDeltaLinkScope((ODataDeltaLinkBase)item, navigationSource, (IEdmEntityType)itemType, selectedProperties, odataUri); + break; + case WriterState.ResourceSet: + scope = this.CreateResourceSetScope((ODataResourceSet)item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet); + if (this.outputContext.Model.IsUserModel()) + { + Debug.Assert(scope is ResourceSetBaseScope, "Create a scope for a resource set that is not a ResourceSetBaseScope"); + ((ResourceSetBaseScope)scope).ResourceTypeValidator = new ResourceSetWithoutExpectedTypeValidator(itemType); + } + + break; + case WriterState.DeltaResourceSet: + scope = this.CreateDeltaResourceSetScope((ODataDeltaResourceSet)item, navigationSource, resourceType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet); + if (this.outputContext.Model.IsUserModel()) + { + Debug.Assert(scope is ResourceSetBaseScope, "Create a scope for a delta resource set that is not a ResourceSetBaseScope"); + ((ResourceSetBaseScope)scope).ResourceTypeValidator = new ResourceSetWithoutExpectedTypeValidator(resourceType); + } + + break; + case WriterState.Property: + scope = this.CreatePropertyInfoScope((ODataPropertyInfo)item, navigationSource, resourceType, selectedProperties, odataUri); + break; + case WriterState.NestedResourceInfo: // fall through + case WriterState.NestedResourceInfoWithContent: + scope = this.CreateNestedResourceInfoScope(state, (ODataNestedResourceInfo)item, navigationSource, itemType, skipWriting, selectedProperties, odataUri); + break; + case WriterState.Start: + scope = new Scope(state, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, /*enableDelta*/ true); + break; + case WriterState.Primitive: // fall through + case WriterState.Stream: // fall through + case WriterState.String: // fall through + case WriterState.Completed: // fall through + case WriterState.Error: + scope = new Scope(state, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, /*enableDelta*/ false); + break; + default: + string errorMessage = Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_Scope_Create_UnreachableCodePath); + Debug.Assert(false, errorMessage); + throw new ODataException(errorMessage); + } + + scope.DerivedTypeConstraints = derivedTypeConstraints?.ToList(); + this.scopeStack.Push(scope); + } + + /// + /// Test to see if for a complex property or a collection of complex property, or a navigation property is declared or not. + /// + /// The nested info in question + /// true if the nested info is undeclared; false if it is not, or if it cannot be determined + private bool IsUndeclared(ODataNestedResourceInfo nestedResourceInfo) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + + if (nestedResourceInfo.SerializationInfo != null) + { + return nestedResourceInfo.SerializationInfo.IsUndeclared; + } + else + { + return this.ParentResourceType != null && (this.ParentResourceType.FindProperty((this.CurrentScope.Item as ODataNestedResourceInfo).Name) == null); + } + } + + /// + /// Asynchronously verifies that calling is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// The resource set/collection to write. + /// A task that represents the asynchronous operation. + private async Task VerifyCanWriteStartResourceSetAsync(bool synchronousCall, ODataResourceSet resourceSet) + { + ExceptionUtils.CheckArgumentNotNull(resourceSet, "resourceSet"); + + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + + await this.StartPayloadInStartStateAsync() + .ConfigureAwait(false); + } + + /// + /// Asynchronously start writing a resource set - implementation of the actual functionality. + /// + /// The resource set to write. + /// A task that represents the asynchronous write operation. + private async Task WriteStartResourceSetImplementationAsync(ODataResourceSet resourceSet) + { + await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.ResourceSet, resourceSet) + .ConfigureAwait(false); + this.EnterScope(WriterState.ResourceSet, resourceSet); + + if (!this.SkipWriting) + { + await this.InterceptExceptionAsync( + async (thisParam, resourceSetParam) => + { + // Verify query count + if (resourceSetParam.Count.HasValue) + { + // Check that Count is not set for requests + if (!thisParam.outputContext.WritingResponse) + { + thisParam.ThrowODataException(Strings.ODataWriterCore_QueryCountInRequest, resourceSetParam); + } + } + + await thisParam.StartResourceSetAsync(resourceSetParam) + .ConfigureAwait(false); + }, resourceSet).ConfigureAwait(false); + } + } + + /// + /// Asynchronously verifies that calling is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// The delta resource set/collection to write. + /// A task that represents the asynchronous operation. + private async Task VerifyCanWriteStartDeltaResourceSetAsync(bool synchronousCall, ODataDeltaResourceSet deltaResourceSet) + { + ExceptionUtils.CheckArgumentNotNull(deltaResourceSet, "deltaResourceSet"); + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + + await this.StartPayloadInStartStateAsync() + .ConfigureAwait(false); + } + + /// + /// Asynchronously start writing a delta resource set - implementation of the actual functionality. + /// + /// The delta resource set to write. + /// A task that represents the asynchronous write operation. + private async Task WriteStartDeltaResourceSetImplementationAsync(ODataDeltaResourceSet deltaResourceSet) + { + await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.ResourceSet, deltaResourceSet) + .ConfigureAwait(false); + this.EnterScope(WriterState.DeltaResourceSet, deltaResourceSet); + + await this.InterceptExceptionAsync( + async (thisParam, deltaResourceSetParam) => + { + if (!thisParam.outputContext.WritingResponse) + { + // Check that links are not set for requests + if (deltaResourceSetParam.NextPageLink != null) + { + thisParam.ThrowODataException(Strings.ODataWriterCore_QueryNextLinkInRequest, deltaResourceSetParam); + } + + if (deltaResourceSetParam.DeltaLink != null) + { + thisParam.ThrowODataException(Strings.ODataWriterCore_QueryDeltaLinkInRequest, deltaResourceSetParam); + } + } + + await thisParam.StartDeltaResourceSetAsync(deltaResourceSetParam) + .ConfigureAwait(false); + }, deltaResourceSet).ConfigureAwait(false); + } + + /// + /// Asynchronously start writing a resource - implementation of the actual functionality. + /// + /// Resource/item to write. + /// A task that represents the asynchronous write operation. + private async Task WriteStartResourceImplementationAsync(ODataResource resource) + { + await this.StartPayloadInStartStateAsync() + .ConfigureAwait(false); + await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.Resource, resource) + .ConfigureAwait(false); + this.EnterScope(WriterState.Resource, resource); + + if (!this.SkipWriting) + { + this.IncreaseResourceDepth(); + await this.InterceptExceptionAsync( + async (thisParam, resourceParam) => + { + if (resourceParam != null) + { + ResourceScope resourceScope = (ResourceScope)thisParam.CurrentScope; + thisParam.ValidateResourceForResourceSet(resourceParam, resourceScope); + await thisParam.PrepareResourceForWriteStartAsync( + resourceScope, + resourceParam, + thisParam.outputContext.WritingResponse, + resourceScope.SelectedProperties).ConfigureAwait(false); + } + + await thisParam.StartResourceAsync(resourceParam) + .ConfigureAwait(false); + }, resource).ConfigureAwait(false); + } + } + + /// + /// Asynchronously start writing a deleted resource - implementation of the actual functionality. + /// + /// Resource/item to write. + /// A task that represents the asynchronous write operation. + private async Task WriteStartDeletedResourceImplementationAsync(ODataDeletedResource resource) + { + Debug.Assert(resource != null, "resource != null"); + + await this.StartPayloadInStartStateAsync() + .ConfigureAwait(false); + await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.Resource, resource) + .ConfigureAwait(false); + this.EnterScope(WriterState.DeletedResource, resource); + this.IncreaseResourceDepth(); + + await this.InterceptExceptionAsync( + async (thisParam, resourceParam) => + { + DeletedResourceScope resourceScope = thisParam.CurrentScope as DeletedResourceScope; + this.ValidateResourceForResourceSet(resourceParam, resourceScope); + + await this.PrepareDeletedResourceForWriteStartAsync( + resourceScope, + resourceParam, + thisParam.outputContext.WritingResponse, + resourceScope.SelectedProperties).ConfigureAwait(false); + await thisParam.StartDeletedResourceAsync(resource) + .ConfigureAwait(false); + }, resource).ConfigureAwait(false); + } + + /// + /// Asynchronously start writing a property - implementation of the actual functionality. + /// + /// Property to write. + /// A task that represents the asynchronous write operation. + private async Task WriteStartPropertyImplementationAsync(ODataPropertyInfo property) + { + this.EnterScope(WriterState.Property, property); + + if (!this.SkipWriting) + { + await this.InterceptExceptionAsync( + async (thisParam, propertyParam) => + { + await thisParam.StartPropertyAsync(propertyParam) + .ConfigureAwait(false); + + if (propertyParam is ODataProperty) + { + PropertyInfoScope scope = thisParam.CurrentScope as PropertyInfoScope; + Debug.Assert(scope != null, "Scope for ODataPropertyInfo is not ODataPropertyInfoScope"); + scope.ValueWritten = true; + } + }, property).ConfigureAwait(false); + } + } + + /// + /// Asynchronously create a write stream - implementation of the actual functionality. + /// + /// A task that represents the asynchronous operation. + /// The value of the TResult parameter contains the stream for writing the binary. + private async Task CreateWriteStreamImplementationAsync() + { + this.EnterScope(WriterState.Stream, null); + Stream underlyingStream = await this.StartBinaryStreamAsync() + .ConfigureAwait(false); + + return new ODataNotificationStream(underlyingStream, /*listener*/ this, /*synchronous*/ false); + } + + /// + /// Asynchronously create a text writer - implementation of the actual functionality. + /// + /// A task that represents the asynchronous operation. + /// The value of the TResult parameter contains the textwriter for writing a string value. + private async Task CreateTextWriterImplementationAsync() + { + this.EnterScope(WriterState.String, null); + TextWriter textWriter = await this.StartTextWriterAsync() + .ConfigureAwait(false); + + return new ODataNotificationWriter(textWriter, /*listener*/ this, /*synchronous*/ false); + } + + /// + /// Asynchronously finish writing a resource set/resource/nested resource info. + /// + /// A task that represents the asynchronous write operation. + private Task WriteEndImplementationAsync() + { + return this.InterceptExceptionAsync( + async (thisParam) => + { + Scope currentScope = thisParam.CurrentScope; + + switch (currentScope.State) + { + case WriterState.Resource: + if (!thisParam.SkipWriting) + { + ODataResource resource = (ODataResource)currentScope.Item; + + await thisParam.EndResourceAsync(resource) + .ConfigureAwait(false); + thisParam.DecreaseResourceDepth(); + } + + break; + case WriterState.DeletedResource: + if (!thisParam.SkipWriting) + { + ODataDeletedResource resource = (ODataDeletedResource)currentScope.Item; + + await thisParam.EndDeletedResourceAsync(resource) + .ConfigureAwait(false); + thisParam.DecreaseResourceDepth(); + } + + break; + case WriterState.ResourceSet: + if (!thisParam.SkipWriting) + { + ODataResourceSet resourceSet = (ODataResourceSet)currentScope.Item; + WriterValidationUtils.ValidateResourceSetAtEnd(resourceSet, !thisParam.outputContext.WritingResponse); + await thisParam.EndResourceSetAsync(resourceSet) + .ConfigureAwait(false); + } + + break; + case WriterState.DeltaLink: + case WriterState.DeltaDeletedLink: + break; + case WriterState.DeltaResourceSet: + if (!thisParam.SkipWriting) + { + ODataDeltaResourceSet deltaResourceSet = (ODataDeltaResourceSet)currentScope.Item; + WriterValidationUtils.ValidateDeltaResourceSetAtEnd(deltaResourceSet, !thisParam.outputContext.WritingResponse); + await thisParam.EndDeltaResourceSetAsync(deltaResourceSet) + .ConfigureAwait(false); + } + + break; + case WriterState.NestedResourceInfo: + if (!thisParam.outputContext.WritingResponse) + { + throw new ODataException(Strings.ODataWriterCore_DeferredLinkInRequest); + } + + if (!thisParam.SkipWriting) + { + ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item; + thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(link); + await thisParam.WriteDeferredNestedResourceInfoAsync(link) + .ConfigureAwait(false); + + thisParam.MarkNestedResourceInfoAsProcessed(link); + } + + break; + case WriterState.NestedResourceInfoWithContent: + if (!thisParam.SkipWriting) + { + ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item; + await thisParam.EndNestedResourceInfoWithContentAsync(link) + .ConfigureAwait(false); + + thisParam.MarkNestedResourceInfoAsProcessed(link); + } + + break; + case WriterState.Property: + { + ODataPropertyInfo property = (ODataPropertyInfo)currentScope.Item; + await thisParam.EndPropertyAsync(property) + .ConfigureAwait(false); + } + + break; + case WriterState.Primitive: + // WriteEnd for WriterState.Primitive is a no-op; just leave scope + break; + case WriterState.Stream: + case WriterState.String: + throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed); + case WriterState.Start: // fall through + case WriterState.Completed: // fall through + case WriterState.Error: // fall through + throw new ODataException(Strings.ODataWriterCore_WriteEndCalledInInvalidState(currentScope.State.ToString())); + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_WriteEnd_UnreachableCodePath)); + } + + await thisParam.LeaveScopeAsync() + .ConfigureAwait(false); + }); + } + + /// + /// Asynchronously write an entity reference link. + /// + /// The entity reference link to write. + /// A task that represents the asynchronous write operation. + private async Task WriteEntityReferenceLinkImplementationAsync(ODataEntityReferenceLink entityReferenceLink) + { + Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null"); + + await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.EntityReferenceLink, null) + .ConfigureAwait(false); + Debug.Assert( + this.CurrentScope.Item is ODataNestedResourceInfo || this.ParentNestedResourceInfoScope.Item is ODataNestedResourceInfo, + "The CheckForNestedResourceInfoWithContent should have verified that entity reference link can only be written inside a nested resource info."); + + if (!this.SkipWriting) + { + await this.InterceptExceptionAsync( + async (thisParam, entityReferenceLinkParam) => + { + WriterValidationUtils.ValidateEntityReferenceLink(entityReferenceLinkParam); + + ODataNestedResourceInfo nestedInfo = thisParam.CurrentScope.Item as ODataNestedResourceInfo; + if (nestedInfo == null) + { + NestedResourceInfoScope nestedResourceInfoScope = thisParam.ParentNestedResourceInfoScope; + Debug.Assert(nestedResourceInfoScope != null); + nestedInfo = (ODataNestedResourceInfo)nestedResourceInfoScope.Item; + } + + await thisParam.WriteEntityReferenceInNavigationLinkContentAsync(nestedInfo, entityReferenceLinkParam) + .ConfigureAwait(false); + }, entityReferenceLink).ConfigureAwait(false); + } + } + + /// + /// Asynchronously checks whether we are currently writing the first top-level element; if so call StartPayloadAsync + /// + /// A task that represents the asynchronous write operation. + private Task StartPayloadInStartStateAsync() + { + if (this.State == WriterState.Start) + { + return this.InterceptExceptionAsync((thisParam) => thisParam.StartPayloadAsync()); + } + + return TaskUtils.CompletedTask; + } + + /// + /// Asynchronously checks whether we are currently writing a nested resource info and switches to NestedResourceInfoWithContent state if we do. + /// + /// + /// What kind of payload kind is being written as the content of a nested resource info. + /// Only Resource Set, Resource or EntityReferenceLink are allowed. + /// + /// The ODataResource or ODataResourceSet to write, or null for ODataEntityReferenceLink. + /// A task that represents the asynchronous operation. + private async Task CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind contentPayloadKind, ODataItem contentPayload) + { + Debug.Assert( + contentPayloadKind == ODataPayloadKind.ResourceSet || contentPayloadKind == ODataPayloadKind.Resource || contentPayloadKind == ODataPayloadKind.EntityReferenceLink, + "Only ResourceSet, Resource or EntityReferenceLink can be specified as a payload kind for a nested resource info content."); + + Scope currentScope = this.CurrentScope; + if (currentScope.State == WriterState.NestedResourceInfo || currentScope.State == WriterState.NestedResourceInfoWithContent) + { + ODataNestedResourceInfo currentNestedResourceInfo = (ODataNestedResourceInfo)currentScope.Item; + + this.InterceptException( + (thisParam, currentNestedResourceInfoParam, contentPayloadKindParam) => + { + if (thisParam.ParentResourceType != null) + { + IEdmStructuralProperty structuralProperty = thisParam.ParentResourceType.FindProperty( + currentNestedResourceInfoParam.Name) as IEdmStructuralProperty; + if (structuralProperty != null) + { + thisParam.CurrentScope.ItemType = structuralProperty.Type.Definition.AsElementType(); + IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource; + + thisParam.CurrentScope.NavigationSource = parentNavigationSource; + } + else + { + IEdmNavigationProperty navigationProperty = thisParam.WriterValidator.ValidateNestedResourceInfo( + currentNestedResourceInfoParam, + thisParam.ParentResourceType, + contentPayloadKindParam); + if (navigationProperty != null) + { + thisParam.CurrentScope.ResourceType = navigationProperty.ToEntityType(); + IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource; + + if (thisParam.CurrentScope.NavigationSource == null) + { + IEdmPathExpression bindingPath; + thisParam.CurrentScope.NavigationSource = parentNavigationSource?.FindNavigationTarget( + navigationProperty, + BindingPathHelper.MatchBindingPath, + thisParam.CurrentScope.ODataUri.Path.Segments, + out bindingPath); + } + } + } + } + }, currentNestedResourceInfo, contentPayloadKind); + + if (currentScope.State == WriterState.NestedResourceInfoWithContent) + { + // If we are already in the NestedResourceInfoWithContent state, it means the caller is trying to write two items + // into the nested resource info content. This is only allowed for collection navigation property in request/response. + if (currentNestedResourceInfo.IsCollection != true) + { + this.ThrowODataException(Strings.ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent, currentNestedResourceInfo); + } + + // Note that we don't invoke duplicate property checker in this case as it's not necessary. + // What happens inside the nested resource info was already validated by the condition above. + // For collection in request we allow any combination anyway. + // For everything else we only allow a single item in the content and thus we will fail above. + } + else + { + // We are writing a nested resource info with content; change the state + this.PromoteNestedResourceInfoScope(contentPayload); + + if (!this.SkipWriting) + { + await this.InterceptExceptionAsync( + async (thisParam, currentNestedResourceInfoParam) => + { + if (!(currentNestedResourceInfoParam.SerializationInfo != null && currentNestedResourceInfoParam.SerializationInfo.IsComplex) + && (thisParam.CurrentScope.ItemType == null || thisParam.CurrentScope.ItemType.IsEntityOrEntityCollectionType())) + { + thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(currentNestedResourceInfoParam); + await thisParam.StartNestedResourceInfoWithContentAsync(currentNestedResourceInfoParam) + .ConfigureAwait(false); + } + }, currentNestedResourceInfo).ConfigureAwait(false); + } + } + } + else + { + if (contentPayloadKind == ODataPayloadKind.EntityReferenceLink) + { + Scope parentScope = this.ParentNestedResourceInfoScope; + Debug.Assert(parentScope != null); + if (parentScope.State != WriterState.NestedResourceInfo && parentScope.State != WriterState.NestedResourceInfoWithContent) + { + this.ThrowODataException(Strings.ODataWriterCore_EntityReferenceLinkWithoutNavigationLink, null); + } + } + } + } + + /// + /// Asynchronously leave the current writer scope and return to the previous scope. + /// When reaching the top-level replace the 'Started' scope with a 'Completed' scope. + /// + /// Note that this method is never called once an error has been written or a fatal exception has been thrown. + private async Task LeaveScopeAsync() + { + Debug.Assert(this.State != WriterState.Error, "this.State != WriterState.Error"); + + this.scopeStack.Pop(); + + // if we are back at the root replace the 'Start' state with the 'Completed' state + if (this.scopeStack.Count == 1) + { + Scope startScope = this.scopeStack.Pop(); + Debug.Assert(startScope.State == WriterState.Start, "startScope.State == WriterState.Start"); + this.PushScope( + WriterState.Completed, + /*item*/ null, + startScope.NavigationSource, + startScope.ResourceType, + /*skipWriting*/ false, + startScope.SelectedProperties, + startScope.ODataUri, + /*derivedTypeConstraints*/ null); + await this.InterceptExceptionAsync((thisParam) => thisParam.EndPayloadAsync()) + .ConfigureAwait(false); + this.NotifyListener(WriterState.Completed); + } + } + + /// + /// Lightweight wrapper for the stack of scopes which exposes a few helper properties for getting parent scopes. + /// + internal sealed class ScopeStack + { + /// + /// Use a list to store the scopes instead of a true stack so that parent/grandparent lookups will be fast. + /// + private readonly List scopes = new List(); + + /// + /// Initializes a new instance of the class. + /// + internal ScopeStack() + { + } + + /// + /// Gets the count of items in the stack. + /// + internal int Count + { + get + { + return this.scopes.Count; + } + } + + /// + /// Gets the scope below the current scope on top of the stack. + /// + internal Scope Parent + { + get + { + Debug.Assert(this.scopes.Count > 1, "this.scopes.Count > 1"); + return this.scopes[this.scopes.Count - 2]; + } + } + + /// + /// Gets the scope below the parent of the current scope on top of the stack. + /// + internal Scope ParentOfParent + { + get + { + Debug.Assert(this.scopes.Count > 2, "this.scopes.Count > 2"); + return this.scopes[this.scopes.Count - 3]; + } + } + + /// + /// Gets the scope below the current scope on top of the stack or null if there is only one item on the stack or the stack is empty. + /// + internal Scope ParentOrNull + { + get + { + return this.Count == 0 ? null : this.Parent; + } + } + + /// + /// Pushes the specified scope onto the stack. + /// + /// The scope. + internal void Push(Scope scope) + { + Debug.Assert(scope != null, "scope != null"); + this.scopes.Add(scope); + } + + /// + /// Pops the current scope off the stack. + /// + /// The popped scope. + internal Scope Pop() + { + Debug.Assert(this.scopes.Count > 0, "this.scopes.Count > 0"); + int last = this.scopes.Count - 1; + Scope current = this.scopes[last]; + this.scopes.RemoveAt(last); + return current; + } + + /// + /// Peeks at the current scope on the top of the stack. + /// + /// The current scope at the top of the stack. + internal Scope Peek() + { + Debug.Assert(this.scopes.Count > 0, "this.scopes.Count > 0"); + return this.scopes[this.scopes.Count - 1]; + } + } + + /// + /// A writer scope; keeping track of the current writer state and an item associated with this state. + /// + internal class Scope + { + /// The writer state of this scope. + private readonly WriterState state; + + /// The item attached to this scope. + private readonly ODataItem item; + + /// Set to true if the content of the scope should not be written. + /// This is used when writing navigation links which were not projected on the owning resource. + private readonly bool skipWriting; + + /// The selected properties for the current scope. + private readonly SelectedPropertiesNode selectedProperties; + + /// The navigation source we are going to write entities for. + private IEdmNavigationSource navigationSource; + + /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used). + private IEdmStructuredType resourceType; + + /// The IEdmType of the item (may not be structured for primitive types). + private IEdmType itemType; + + /// The odata uri info for current scope. + private ODataUri odataUri; + + /// Whether we are in the context of writing a delta collection. + private bool enableDelta; + + /// + /// Constructor creating a new writer scope. + /// + /// The writer state of this scope. + /// The item attached to this scope. + /// The navigation source we are going to write resource set for. + /// The type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of this scope should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// Whether we are in the context of writing a delta collection. + internal Scope(WriterState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool enableDelta) + { + this.state = state; + this.item = item; + this.itemType = itemType; + this.resourceType = itemType as IEdmStructuredType; + this.navigationSource = navigationSource; + this.skipWriting = skipWriting; + this.selectedProperties = selectedProperties; + this.odataUri = odataUri; + this.enableDelta = enableDelta; + } + + /// + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// + public IEdmStructuredType ResourceType + { + get + { + return this.resourceType; + } + + set + { + this.resourceType = value; + this.itemType = value; + } + } + + /// + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// + public IEdmType ItemType + { + get + { + return this.itemType; + } + + set + { + this.itemType = value; + this.resourceType = value as IEdmStructuredType; + } + } + + /// + /// The writer state of this scope. + /// + internal WriterState State + { + get + { + return this.state; + } + } + + /// + /// The item attached to this scope. + /// + internal ODataItem Item + { + get + { + return this.item; + } + } + + /// The navigation source we are going to write entities for. + internal IEdmNavigationSource NavigationSource + { + get + { + return this.navigationSource; + } + + set + { + this.navigationSource = value; + } + } + + /// The selected properties for the current scope. + internal SelectedPropertiesNode SelectedProperties + { + get + { + return this.selectedProperties; + } + } + + /// The odata Uri for the current scope. + internal ODataUri ODataUri + { + get + { + Debug.Assert(this.odataUri != null, "this.odataUri != null"); + return this.odataUri; + } + } + + /// + /// Set to true if the content of this scope should not be written. + /// + internal bool SkipWriting + { + get + { + return this.skipWriting; + } + } + + /// + /// True if we are in the process of writing a delta collection. + /// + public bool EnableDelta + { + get + { + return this.enableDelta; + } + + protected set + { + this.enableDelta = value; + } + } + + /// Gets or sets the derived type constraints for the current scope. + internal List DerivedTypeConstraints { get; set; } + } + + /// + /// A base scope for a resourceSet. + /// + internal abstract class ResourceSetBaseScope : Scope + { + /// The serialization info for the current resourceSet. + private readonly ODataResourceSerializationInfo serializationInfo; + + /// + /// The to use for entries in this resourceSet. + /// + private ResourceSetWithoutExpectedTypeValidator resourceTypeValidator; + + /// The number of entries in this resourceSet seen so far. + private int resourceCount; + + /// Maintains the write status for each annotation using its key. + private InstanceAnnotationWriteTracker instanceAnnotationWriteTracker; + + /// The type context to answer basic questions regarding the type info of the resource. + private ODataResourceTypeContext typeContext; + + /// + /// Constructor to create a new resource set scope. + /// + /// The writer state for the scope. + /// The resourceSet for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + internal ResourceSetBaseScope(WriterState writerState, ODataResourceSetBase resourceSet, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(writerState, resourceSet, navigationSource, itemType, skipWriting, selectedProperties, odataUri, writerState == WriterState.DeltaResourceSet) + { + this.serializationInfo = resourceSet.SerializationInfo; + } + + /// + /// The number of entries in this resource Set seen so far. + /// + internal int ResourceCount + { + get + { + return this.resourceCount; + } + + set + { + this.resourceCount = value; + } + } + + /// + /// Tracks the write status of the annotations. + /// + internal InstanceAnnotationWriteTracker InstanceAnnotationWriteTracker + { + get + { + if (this.instanceAnnotationWriteTracker == null) + { + this.instanceAnnotationWriteTracker = new InstanceAnnotationWriteTracker(); + } + + return this.instanceAnnotationWriteTracker; + } + } + + /// + /// Validator for resource type. + /// + internal ResourceSetWithoutExpectedTypeValidator ResourceTypeValidator + { + get + { + return this.resourceTypeValidator; + } + + set + { + this.resourceTypeValidator = value; + } + } + + /// + /// Gets or creates the type context to answer basic questions regarding the type info of the resource. + /// + /// True if writing a response payload, false otherwise. + /// The type context to answer basic questions regarding the type info of the resource. + internal ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse) + { + if (this.typeContext == null) + { + // For Entity, currently we check the navigation source. + // For Complex, we don't have navigation source, So we shouldn't check it. + // If ResourceType is not provided, serialization info or navigation source info should be provided. + bool throwIfMissingTypeInfo = writingResponse && (this.ResourceType == null || this.ResourceType.TypeKind == EdmTypeKind.Entity); + + this.typeContext = ODataResourceTypeContext.Create( + this.serializationInfo, + this.NavigationSource, + EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource), + this.ResourceType, + throwIfMissingTypeInfo); + } + + return this.typeContext; + } + } + + /// + /// A scope for a resource set. + /// + internal abstract class ResourceSetScope : ResourceSetBaseScope + { + /// + /// Constructor to create a new resource set scope. + /// + /// The resource set for the new scope. + /// The navigation source we are going to write resource set for. + /// The type of the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + protected ResourceSetScope(ODataResourceSet item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(WriterState.ResourceSet, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri) + { + } + } + + /// + /// A scope for a delta resource set. + /// + internal abstract class DeltaResourceSetScope : ResourceSetBaseScope + { + /// + /// Constructor to create a new resource set scope. + /// + /// The resource set for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type of the items in the resource set to be written (or null if the entity set base type should be used). + /// The selected properties of this scope. + /// The ODataUri info of this scope. + protected DeltaResourceSetScope(ODataDeltaResourceSet item, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(WriterState.DeltaResourceSet, item, navigationSource, resourceType, false /*skip writing*/, selectedProperties, odataUri) + { + } + + /// + /// The context uri info created for this scope. + /// + public ODataContextUrlInfo ContextUriInfo { get; set; } + } + + /// + /// A base scope for a resource. + /// + internal class ResourceBaseScope : Scope + { + /// Checker to detect duplicate property names. + private readonly IDuplicatePropertyNameChecker duplicatePropertyNameChecker; + + /// The serialization info for the current resource. + private readonly ODataResourceSerializationInfo serializationInfo; + + /// The resource type which was derived from the model (may be either the same as structured type or its base type. + private IEdmStructuredType resourceTypeFromMetadata; + + /// The type context to answer basic questions regarding the type info of the resource. + private ODataResourceTypeContext typeContext; + + /// Maintains the write status for each annotation using its key. + private InstanceAnnotationWriteTracker instanceAnnotationWriteTracker; + + /// + /// Constructor to create a new resource scope. + /// + /// The writer state of this scope. + /// The resource for the new scope. + /// The serialization info for the current resource. + /// The navigation source we are going to write resource set for. + /// The type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The The settings of the writer. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + internal ResourceBaseScope(WriterState state, ODataResourceBase resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(state, resource, navigationSource, itemType, skipWriting, selectedProperties, odataUri, /*enableDelta*/ true) + { + Debug.Assert(writerSettings != null, "writerBehavior != null"); + + if (resource != null) + { + duplicatePropertyNameChecker = writerSettings.Validator.CreateDuplicatePropertyNameChecker(); + } + + this.serializationInfo = serializationInfo; + } + + /// + /// The structured type which was derived from the model, i.e. the expected structured type, which may be either the same as structured type or its base type. + /// For example, if we are writing a resource set of Customers and the current resource is of DerivedCustomer, this.ResourceTypeFromMetadata would be Customer and this.ResourceType would be DerivedCustomer. + /// + public IEdmStructuredType ResourceTypeFromMetadata + { + get + { + return this.resourceTypeFromMetadata; + } + + internal set + { + this.resourceTypeFromMetadata = value; + } + } + + /// + /// The serialization info for the current resource. + /// + public ODataResourceSerializationInfo SerializationInfo + { + get { return this.serializationInfo; } + } + + /// + /// Checker to detect duplicate property names. + /// + internal IDuplicatePropertyNameChecker DuplicatePropertyNameChecker + { + get + { + return duplicatePropertyNameChecker; + } + } + + /// + /// Tracks the write status of the annotations. + /// + internal InstanceAnnotationWriteTracker InstanceAnnotationWriteTracker + { + get + { + if (this.instanceAnnotationWriteTracker == null) + { + this.instanceAnnotationWriteTracker = new InstanceAnnotationWriteTracker(); + } + + return this.instanceAnnotationWriteTracker; + } + } + + /// + /// Gets or creates the type context to answer basic questions regarding the type info of the resource. + /// + /// True if writing a response payload, false otherwise. + /// The type context to answer basic questions regarding the type info of the resource. + public ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse) + { + if (this.typeContext == null) + { + IEdmStructuredType expectedResourceType = this.ResourceTypeFromMetadata ?? this.ResourceType; + + // For entity, we will check the navigation source info + bool throwIfMissingTypeInfo = writingResponse && (expectedResourceType == null || expectedResourceType.TypeKind == EdmTypeKind.Entity); + + this.typeContext = ODataResourceTypeContext.Create( + this.serializationInfo, + this.NavigationSource, + EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource), + expectedResourceType, + throwIfMissingTypeInfo); + } + + return this.typeContext; + } + } + + /// + /// A base scope for a resource. + /// + internal class ResourceScope : ResourceBaseScope + { + /// + /// Constructor to create a new resource scope. + /// + /// The resource for the new scope. + /// The serialization info for the current resource. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The The settings of the writer. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + protected ResourceScope(ODataResource resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(WriterState.Resource, resource, serializationInfo, navigationSource, resourceType, skipWriting, writerSettings, selectedProperties, odataUri) + { + } + } + + /// + /// Base class for DeletedResourceScope. + /// + internal class DeletedResourceScope : ResourceBaseScope + { + /// + /// Constructor to create a new resource scope. + /// + /// The resource for the new scope. + /// The serialization info for the current resource. + /// The navigation source we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The The settings of the writer. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + protected DeletedResourceScope(ODataDeletedResource resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType entityType, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(WriterState.DeletedResource, resource, serializationInfo, navigationSource, entityType, false /*skipWriting*/, writerSettings, selectedProperties, odataUri) + { + } + } + + /// + /// A scope for a delta link. + /// + internal abstract class DeltaLinkScope : Scope + { + /// The serialization info for the current link. + private readonly ODataResourceSerializationInfo serializationInfo; + + /// + /// Fake entity type to be passed to context. + /// + private readonly EdmEntityType fakeEntityType = new EdmEntityType("MyNS", "Fake"); + + /// The type context to answer basic questions regarding the type info of the link. + private ODataResourceTypeContext typeContext; + + /// + /// Constructor to create a new delta link scope. + /// + /// The writer state of this scope. + /// The link for the new scope. + /// The serialization info for the current resource. + /// The navigation source we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The selected properties of this scope. + /// The ODataUri info of this scope. + protected DeltaLinkScope(WriterState state, ODataItem link, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType entityType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(state, link, navigationSource, entityType, /*skipWriting*/false, selectedProperties, odataUri, /*enableDelta*/ false) + { + Debug.Assert(link != null, "link != null"); + Debug.Assert( + state == WriterState.DeltaLink && link is ODataDeltaLink || + state == WriterState.DeltaDeletedLink && link is ODataDeltaDeletedLink, + "link must be either DeltaLink or DeltaDeletedLink."); + + this.serializationInfo = serializationInfo; + } + + /// + /// Gets or creates the type context to answer basic questions regarding the type info of the resource. + /// + /// Whether writing Json payload. Should always be true. + /// The type context to answer basic questions regarding the type info of the resource. + public ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse = true) + { + if (this.typeContext == null) + { + this.typeContext = ODataResourceTypeContext.Create( + this.serializationInfo, + this.NavigationSource, + EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource), + this.fakeEntityType, + writingResponse); + } + + return this.typeContext; + } + } + + /// + /// A scope for writing a single property within a resource. + /// + internal class PropertyInfoScope : Scope + { + /// + /// Constructor to create a new property scope. + /// + /// The property for the new scope. + /// The navigation source. + /// The structured type for the resource containing the property to be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + internal PropertyInfoScope(ODataPropertyInfo property, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(WriterState.Property, property, navigationSource, resourceType, /*skipWriting*/ false, selectedProperties, odataUri, /*enableDelta*/ true) + { + ValueWritten = false; + } + + public ODataPropertyInfo Property + { + get + { + Debug.Assert(this.Item is ODataProperty, "The item of a property scope is not an item."); + return this.Item as ODataProperty; + } + } + + internal bool ValueWritten { get; set; } + } + + /// + /// A scope for a nested resource info. + /// + internal class NestedResourceInfoScope : Scope + { + /// + /// Constructor to create a new nested resource info scope. + /// + /// The writer state for the new scope. + /// The nested resource info for the new scope. + /// The navigation source we are going to write resource set for. + /// The type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// The scope of the parent. + internal NestedResourceInfoScope(WriterState writerState, ODataNestedResourceInfo navLink, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, Scope parentScope) + : base(writerState, navLink, navigationSource, itemType, skipWriting, selectedProperties, odataUri, parentScope.EnableDelta) + { + this.parentScope = parentScope; + } + + /// Scope of the parent + protected Scope parentScope; + + /// + /// Clones this nested resource info scope and sets a new writer state. + /// + /// The to set. + /// The cloned nested resource info scope with the specified writer state. + internal virtual NestedResourceInfoScope Clone(WriterState newWriterState) + { + return new NestedResourceInfoScope(newWriterState, (ODataNestedResourceInfo)this.Item, this.NavigationSource, this.ItemType, this.SkipWriting, this.SelectedProperties, this.ODataUri, parentScope) + { + DerivedTypeConstraints = this.DerivedTypeConstraints, + }; + } + } + } +} diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataEntryMetadataContextTest.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataEntryMetadataContextTest.cs index 95a3e803bf..90a0fc9669 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataEntryMetadataContextTest.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataEntryMetadataContextTest.cs @@ -196,8 +196,8 @@ public void ShouldGetKeyPropertiesBasedOnMetadata() { this.entry.Properties = new[] { - new ODataProperty { Name = "ID1", Value = 1, SerializationInfo = new ODataPropertySerializationInfo { PropertyKind = ODataPropertyKind.Key } }, - new ODataProperty { Name = "ID2", Value = 2, SerializationInfo = new ODataPropertySerializationInfo { PropertyKind = ODataPropertyKind.Key } }, + new ODataProperty { Name = "ID1", Value = 1 }, + new ODataProperty { Name = "ID2", Value = 2 }, new ODataProperty { Name = "ID3", Value = 3 }, }; @@ -206,6 +206,7 @@ public void ShouldGetKeyPropertiesBasedOnMetadata() Assert.Contains(keys, p => p.Key == "ID2"); Assert.Contains(keys, p => p.Key == "ID3"); } + #endregion KeyProperties #region ETagProperties diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Evaluation/AutoComputePayloadMetadataInJsonIntegrationTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Evaluation/AutoComputePayloadMetadataInJsonIntegrationTests.cs index 0ed8d16a3e..68032a126b 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Evaluation/AutoComputePayloadMetadataInJsonIntegrationTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Evaluation/AutoComputePayloadMetadataInJsonIntegrationTests.cs @@ -512,6 +512,150 @@ static AutoComputePayloadMetadataInJsonIntegrationTests() Model.AddElement(function4); } + public enum SerializationType + { + NoSerializationInfo, + ResourceSerializationInfo, + ResourceAndPropertySerializationInfo + } + + [Theory] + [InlineData(SerializationType.NoSerializationInfo, /*fullMetadata*/ false)] + [InlineData(SerializationType.ResourceAndPropertySerializationInfo, /*fullMetadata*/ false)] + [InlineData(SerializationType.ResourceSerializationInfo, /*fullMetadata*/ false)] + [InlineData(SerializationType.NoSerializationInfo, /*fullMetadata*/ true)] + [InlineData(SerializationType.ResourceAndPropertySerializationInfo, /*fullMetadata*/ true)] + [InlineData(SerializationType.ResourceSerializationInfo, /*fullMetadata*/ true)] + public void WritingNestedResourceInfoWithVariousSerializationInfoSucceeds(SerializationType serializationType, bool fullMetadata) + { + // setup model + var model = new EdmModel(); + var entityType = new EdmEntityType("NS", "entityType"); + entityType.AddKeys( + entityType.AddStructuralProperty("keyProperty", EdmPrimitiveTypeKind.Int64)); + + var nestedEntityType = new EdmEntityType("NS", "nestedEntityType"); + nestedEntityType.AddKeys( + nestedEntityType.AddStructuralProperty("keyProperty", EdmPrimitiveTypeKind.Int64)); + + entityType.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo + { + Name = "nestedEntities", + Target = nestedEntityType, + TargetMultiplicity = EdmMultiplicity.Many, + ContainsTarget = true + }); + + var container = new EdmEntityContainer("NS", "Container"); + var entitySet = container.AddEntitySet("EntitySet", entityType); + + model.AddElements(new IEdmSchemaElement[] { entityType, nestedEntityType, container }); + + // setup writer + string str; + using (var stream = new MemoryStream()) + { + var message = new InMemoryMessage { Stream = stream }; + message.SetHeader("Content-Type", fullMetadata ? "application/json;odata.metadata=full" : "application/json"); + var settings = new ODataMessageWriterSettings + { + ODataUri = new ODataUri + { + ServiceRoot = new Uri("http://svc/"), + Path = new ODataUriParser(model, new Uri("EntitySet", UriKind.Relative)).ParsePath() + }, + }; + var writer = new ODataMessageWriter((IODataResponseMessage)message, settings, model); + + // write payload + var entitySetWriter = writer.CreateODataResourceSetWriter(entitySet, entitySet.EntityType()); + entitySetWriter.WriteStart(new ODataResourceSet()); + var resource = new ODataResource + { + Properties = new[] + { + new ODataProperty { + Name = "keyProperty", + Value = 1L, + SerializationInfo = serializationType == SerializationType.ResourceAndPropertySerializationInfo ? + new ODataPropertySerializationInfo { + PropertyKind = ODataPropertyKind.Key + } : null + } + }, + TypeName = serializationType == SerializationType.NoSerializationInfo ? null : entityType.FullName + }; + + if (serializationType != SerializationType.NoSerializationInfo) + { + resource.SerializationInfo = new ODataResourceSerializationInfo + { + NavigationSourceName = "EntitySet", + NavigationSourceKind = EdmNavigationSourceKind.EntitySet, + NavigationSourceEntityTypeName = entityType.FullName, + ExpectedTypeName = entityType.FullName, + IsFromCollection = true + }; + } + + entitySetWriter.WriteStart(resource); + entitySetWriter.WriteStart( + new ODataNestedResourceInfo + { + Name = "nestedEntities", + } + ); + entitySetWriter.WriteStart(new ODataResourceSet()); + var entityValue = new ODataResource + { + TypeName = "NS.nestedEntityType", + Properties = new[] + { + new ODataProperty { Name = "keyProperty", Value = 1L } + } + }; + entitySetWriter.WriteStart(entityValue); + entitySetWriter.WriteEnd(); // nestedEntity + entitySetWriter.WriteEnd(); // nested resourceSet + entitySetWriter.WriteEnd(); // nestedInfo + entitySetWriter.WriteEnd(); // entity + entitySetWriter.WriteEnd(); // resourceSet + str = Encoding.UTF8.GetString(stream.ToArray()); + } + + var typeAnnotation = + serializationType == SerializationType.NoSerializationInfo ? "" : + "\"@odata.type\":\"#NS.entityType\","; + + var expected = fullMetadata ? + "{\"@odata.context\":\"http://svc/$metadata#EntitySet\"," + + "\"value\":[{" + + typeAnnotation + + "\"@odata.id\":\"EntitySet(1)\"," + + "\"@odata.editLink\":\"EntitySet(1)\"," + + "\"keyProperty@odata.type\":\"#Int64\"," + + "\"keyProperty\":1," + + "\"nestedEntities@odata.associationLink\":\"http://svc/EntitySet(1)/nestedEntities/$ref\"," + + "\"nestedEntities@odata.navigationLink\":\"http://svc/EntitySet(1)/nestedEntities\"," + + "\"nestedEntities\":[{" + + "\"@odata.type\":\"#NS.nestedEntityType\"," + + "\"@odata.id\":\"EntitySet(1)/nestedEntities(1)\"," + + "\"@odata.editLink\":\"EntitySet(1)/nestedEntities(1)\"," + + "\"keyProperty@odata.type\":\"#Int64\"," + + "\"keyProperty\":1" + + "}]" + + "}]}" : + "{\"@odata.context\":\"http://svc/$metadata#EntitySet\"," + + "\"value\":[{" + + "\"keyProperty\":1," + + "\"nestedEntities\":[{" + + "\"keyProperty\":1" + + "}]" + + "}]}"; + + Assert.Equal(expected, str); + } + [Fact] public void WritingDynamicComplexPropertyWithModelSpecifiedInFullMetadataMode() { diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataReaderDerivedTypeConstraintTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataReaderDerivedTypeConstraintTests.cs index 121a95f8ff..7ca0ab6709 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataReaderDerivedTypeConstraintTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataReaderDerivedTypeConstraintTests.cs @@ -516,7 +516,7 @@ public void ReadingingSingleComplexProppertyWithNotAllowedTypeWorksWithoutDerive // Negative test case --Structural property has the derived type constraint. SetDerivedTypeAnnotation(this.edmModel, locationProperty, "NS.CnAddress"); - Action test = () => ReadEntityPayload(payload, this.edmModel, this.edmMe, this.edmCustomerType); ; + Action test = () => ReadEntityPayload(payload, this.edmModel, this.edmMe, this.edmCustomerType); var exception = Assert.Throws(test); Assert.Equal(Strings.ReaderValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint("NS.UsAddress", "nested resource", "Location"), exception.Message); } diff --git a/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/Serialization/EdmModelCsdlSerializationVisitorTests.cs b/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/Serialization/EdmModelCsdlSerializationVisitorTests.cs index 057c7b7025..b5954e35ba 100644 --- a/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/Serialization/EdmModelCsdlSerializationVisitorTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/Serialization/EdmModelCsdlSerializationVisitorTests.cs @@ -2026,7 +2026,7 @@ internal void VisitAndVerifyXml(Action testAct { Indent = indent, ConformanceLevel = ConformanceLevel.Auto - }); ; + }); var schemaWriter = new EdmModelCsdlSchemaXmlWriter(model, xmlWriter, edmxVersion); var visitor = new EdmModelCsdlSerializationVisitor(model, schemaWriter);