/// <summary>
        /// Writes a stream property to the ATOM payload
        /// </summary>
        /// <param name="streamProperty">The stream property to create the payload for.</param>
        /// <param name="owningType">The <see cref="IEdmEntityType"/> instance for which the stream property defined on.</param>
        /// <param name="duplicatePropertyNamesChecker">The checker instance for duplicate property names.</param>
        /// <param name="projectedProperties">Set of projected properties, or null if all properties should be written.</param>
        internal void WriteStreamProperty(
            ODataProperty streamProperty,
            IEdmEntityType owningType,
            DuplicatePropertyNamesChecker duplicatePropertyNamesChecker,
            ProjectedPropertiesAnnotation projectedProperties)
        {
            Debug.Assert(streamProperty != null, "Stream property must not be null.");
            Debug.Assert(streamProperty.Value != null, "The media resource of the stream property must not be null.");

            WriterValidationUtils.ValidatePropertyNotNull(streamProperty);
            string propertyName = streamProperty.Name;

            if (projectedProperties.ShouldSkipProperty(propertyName))
            {
                return;
            }

            WriterValidationUtils.ValidatePropertyName(propertyName);
            duplicatePropertyNamesChecker.CheckForDuplicatePropertyNames(streamProperty);
            IEdmProperty edmProperty = WriterValidationUtils.ValidatePropertyDefined(streamProperty.Name, owningType);

            WriterValidationUtils.ValidateStreamReferenceProperty(streamProperty, edmProperty, this.Version, this.WritingResponse);
            ODataStreamReferenceValue streamReferenceValue = (ODataStreamReferenceValue)streamProperty.Value;

            WriterValidationUtils.ValidateStreamReferenceValue(streamReferenceValue, false /*isDefaultStream*/);
            if (owningType != null && owningType.IsOpen && edmProperty == null)
            {
                ValidationUtils.ValidateOpenPropertyValue(streamProperty.Name, streamReferenceValue);
            }

            AtomStreamReferenceMetadata streamReferenceMetadata = streamReferenceValue.GetAnnotation <AtomStreamReferenceMetadata>();
            string contentType = streamReferenceValue.ContentType;
            string linkTitle   = streamProperty.Name;

            Uri readLink = streamReferenceValue.ReadLink;

            if (readLink != null)
            {
                string readLinkRelation = AtomUtils.ComputeStreamPropertyRelation(streamProperty, false);

                AtomLinkMetadata readLinkMetadata = streamReferenceMetadata == null ? null : streamReferenceMetadata.SelfLink;
                AtomLinkMetadata mergedMetadata   = ODataAtomWriterMetadataUtils.MergeLinkMetadata(readLinkMetadata, readLinkRelation, readLink, linkTitle, contentType);
                this.atomEntryMetadataSerializer.WriteAtomLink(mergedMetadata, null /* etag */);
            }

            Uri editLink = streamReferenceValue.EditLink;

            if (editLink != null)
            {
                string editLinkRelation = AtomUtils.ComputeStreamPropertyRelation(streamProperty, true);

                AtomLinkMetadata editLinkMetadata = streamReferenceMetadata == null ? null : streamReferenceMetadata.EditLink;
                AtomLinkMetadata mergedMetadata   = ODataAtomWriterMetadataUtils.MergeLinkMetadata(editLinkMetadata, editLinkRelation, editLink, linkTitle, contentType);
                this.atomEntryMetadataSerializer.WriteAtomLink(mergedMetadata, streamReferenceValue.ETag);
            }
        }
        /// <summary>
        /// Writes the edit-media link for an entry.
        /// </summary>
        /// <param name="mediaResource">The media resource representing the MR of the entry to write.</param>
        internal void WriteEntryMediaEditLink(ODataStreamReferenceValue mediaResource)
        {
            Debug.Assert(mediaResource != null, "mediaResource != null");

            Uri mediaEditLink = mediaResource.EditLink;

            Debug.Assert(mediaEditLink != null || mediaResource.ETag == null, "The default stream edit link and etag should have been validated by now.");
            if (mediaEditLink != null)
            {
                AtomStreamReferenceMetadata streamReferenceMetadata = mediaResource.GetAnnotation <AtomStreamReferenceMetadata>();
                AtomLinkMetadata            mediaEditMetadata       = streamReferenceMetadata == null ? null : streamReferenceMetadata.EditLink;
                AtomLinkMetadata            mergedLinkMetadata      =
                    ODataAtomWriterMetadataUtils.MergeLinkMetadata(
                        mediaEditMetadata,
                        AtomConstants.AtomEditMediaRelationAttributeValue,
                        mediaEditLink,
                        null /* title */,
                        null /* mediaType */);

                this.atomEntryMetadataSerializer.WriteAtomLink(mergedLinkMetadata, mediaResource.ETag);
            }
        }
        /// <summary>
        /// Reads a stream property link in an atom:entry.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <param name="streamPropertyName">The name of the stream property that is being read.</param>
        /// <param name="linkRelation">The rel attribute value for the link.</param>
        /// <param name="linkHRef">The href attribute value for the link (or null if the href attribute was not present).</param>
        /// <param name="editLink">true if we are reading an edit link; otherwise false.</param>
        /// <returns>true if the stream property link was read; otherwise false.</returns>
        /// <remarks>
        /// Pre-Condition:   XmlNodeType.Element atom:link  - The atom:link element to read.
        /// Post-Condition:  Any                            - The node after the atom:link element if the link was read by this method.
        ///                  XmlNodeType.Element atom:link  - The atom:link element to read if the link was not read by this method.
        /// </remarks>
        private bool ReadStreamPropertyLinkInEntry(IODataAtomReaderEntryState entryState, string streamPropertyName, string linkRelation, string linkHRef, bool editLink)
        {
            if (!this.ReadingResponse)
            {
                // Ignore stream properties in requests as they cannot appear there.
                // Reader versioning: do not recognize stream properties if MPV < V3, but read them even in <V3 payload if MPV >= V3.
                return false;
            }

            // Fail on stream property with empty name, no backward compat reasons, we can fail.
            if (streamPropertyName.Length == 0)
            {
                throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithEmptyName);
            }

            ODataStreamReferenceValue streamReferenceValue = this.GetNewOrExistingStreamPropertyValue(entryState, streamPropertyName);
            Debug.Assert(streamReferenceValue != null, "streamReferenceValue != null");

            AtomStreamReferenceMetadata atomStreamMetadata = null;
            if (this.ReadAtomMetadata)
            {
                // First, check if there is an existing metadata annotation on the stream reference value.
                atomStreamMetadata = streamReferenceValue.GetAnnotation<AtomStreamReferenceMetadata>();

                // If not, create a new metadata annotation.
                if (atomStreamMetadata == null)
                {
                    atomStreamMetadata = new AtomStreamReferenceMetadata();
                    streamReferenceValue.SetAnnotation(atomStreamMetadata);
                }
            }

            // set the edit-link or the read-link
            // We allow missing hrefs on atom:link elements
            Uri href = linkHRef == null ? null : this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri);

            if (editLink)
            {
                // edit-link
                // We fail if we find two edit links for the same stream property.
                // WCF DS client behavior is to fail if we find two edit or read links for the same stream property.
                if (streamReferenceValue.EditLink != null)
                {
                    throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithMultipleEditLinks(streamPropertyName));
                }

                streamReferenceValue.EditLink = href;

                if (this.ReadAtomMetadata)
                {
                    atomStreamMetadata.EditLink = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef);
                }
            }
            else
            {
                // read-link
                // We fail if we find two read links for the same stream property.
                // WCF DS client behavior is to fail if we find two edit or read links for the same stream property.
                if (streamReferenceValue.ReadLink != null)
                {
                    throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithMultipleReadLinks(streamPropertyName));
                }

                streamReferenceValue.ReadLink = href;

                if (this.ReadAtomMetadata)
                {
                    atomStreamMetadata.SelfLink = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef);
                }
            }

            // set the ContentType
            string contentType = this.XmlReader.GetAttribute(this.AtomTypeAttributeName, this.EmptyNamespace);

            if (contentType != null && streamReferenceValue.ContentType != null)
            {
                // If we find two different content types, we fail since we have only one property to store it in (and it doesn't makes sense for a single stream
                // property to have two different content types).
                if (!HttpUtils.CompareMediaTypeNames(contentType, streamReferenceValue.ContentType))
                {
                    throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithMultipleContentTypes(streamPropertyName));
                }
            }

            streamReferenceValue.ContentType = contentType;

            // set the ETag
            if (editLink)
            {
                string etag = this.XmlReader.GetAttribute(this.ODataETagAttributeName, this.XmlReader.ODataMetadataNamespace);
                streamReferenceValue.ETag = etag;
            }

            // [Astoria-ODataLib-Integration] Should we skip the atom:link element for named streams.
            // Skip the entire Atom:link element content. The client does not expect any content to be there. Also the client  
            // does not write link elements when creating requests and the server does not really need to care about these.
            this.XmlReader.Skip();
            return true;
        }
        /// <summary>
        /// Get the stream reference value for media resource (the default stream of an entity).
        /// </summary>
        /// <param name="entityToSerialize">Entity that is currently being serialized.</param>
        /// <param name="title">The title for the element being written.</param>
        /// <returns>
        /// An instance of ODataStreamReferenceValue containing the metadata about the media resource.
        /// </returns>
        private ODataStreamReferenceValue GetMediaResource(EntityToSerialize entityToSerialize, string title)
        {
            Debug.Assert(entityToSerialize.Entity != null, "element != null");
            Debug.Assert(entityToSerialize.ResourceType != null && entityToSerialize.ResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "type != null && type.ResourceTypeKind == ResourceTypeKind.EntityType");
            Debug.Assert(!string.IsNullOrEmpty(title), "!string.IsNullOrEmpty(title)");

            ODataStreamReferenceValue mediaResource = null;

            // Handle MLE
            if (entityToSerialize.ResourceType.IsMediaLinkEntry)
            {
                string mediaETag;
                Uri readStreamUri;
                string mediaContentType;

                Debug.Assert(entityToSerialize.ResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "type.ResourceTypeKind == ResourceTypeKind.EntityType");
                this.Service.StreamProvider.GetStreamDescription(entityToSerialize.Entity, null /*null for MLE*/, this.Service.OperationContext, out mediaETag, out readStreamUri, out mediaContentType);
                Debug.Assert(WebUtil.IsETagValueValid(mediaETag, true), "WebUtil.IsETagValueValid(mediaETag, true)");
                Debug.Assert(!string.IsNullOrEmpty(mediaContentType), "!string.IsNullOrEmpty(mediaContentType)");

                mediaResource = new ODataStreamReferenceValue();

                // build the stream's edit link lazily to avoid creating the entity's edit link if it is not needed.
                SimpleLazy<Uri> lazyStreamEditLink = new SimpleLazy<Uri>(() => RequestUriProcessor.AppendEscapedSegment(entityToSerialize.SerializedKey.RelativeEditLink, XmlConstants.UriValueSegment));

                this.PayloadMetadataPropertyManager.SetEditLink(mediaResource, () => lazyStreamEditLink.Value);

                this.PayloadMetadataPropertyManager.SetContentType(mediaResource, mediaContentType);

                // If the stream provider did not provider a read link, then we should use the edit link as the read link.
                this.PayloadMetadataPropertyManager.SetReadLink(mediaResource, () => readStreamUri ?? lazyStreamEditLink.Value);
#pragma warning disable 618
                if (this.contentFormat == ODataFormat.Atom)
#pragma warning restore 618
                {
                    AtomStreamReferenceMetadata mediaResourceAtom = new AtomStreamReferenceMetadata() { EditLink = new AtomLinkMetadata { Title = title } };
                    mediaResource.SetAnnotation(mediaResourceAtom);
                }

                if (!string.IsNullOrEmpty(mediaETag))
                {
                    this.PayloadMetadataPropertyManager.SetETag(mediaResource, mediaETag);
                }
            }

            return mediaResource;
        }
 /// <summary>
 /// Visits an ATOM stream reference metadata.
 /// </summary>
 /// <param name="atomStreamReferenceMetadata">The stream reference metadata to visit.</param>
 protected virtual void VisitAtomStreamReferenceMetadata(AtomStreamReferenceMetadata atomStreamReferenceMetadata)
 {
     this.VisitAtomMetadata(atomStreamReferenceMetadata.EditLink);
     this.VisitAtomMetadata(atomStreamReferenceMetadata.SelfLink);
 }
        public void NamedStreamReadAndEditLinkMetadataWriterTest()
        {
            Func<XElement, XElement> fragmentExtractor = (e) => e.Elements(TestAtomConstants.AtomXNamespace + "link").Last();

            var allTestCases = linkMetadataTestCases.ConcatSingle(incorrectMediaTypeLinkMetadataTestCases).ConcatSingle(incorrectTitleLinkMetadataTestCases);

            var readLinkTestDescriptors = allTestCases.Select(testCase =>
            {
                ODataEntry entry = ObjectModelUtils.CreateDefaultEntryWithAtomMetadata();
                ODataStreamReferenceValue streamReferenceValue = new ODataStreamReferenceValue()
                {
                    ReadLink = new Uri(readLinkHref),
                    ContentType = linkMediaType,
                };

                AtomStreamReferenceMetadata streamReferenceMetadata = new AtomStreamReferenceMetadata()
                {
                    SelfLink = testCase.LinkMetadata("http://docs.oasis-open.org/odata/ns/mediaresource/Stream", readLinkHref)
                };

                streamReferenceValue.SetAnnotation<AtomStreamReferenceMetadata>(streamReferenceMetadata);
                entry.Properties = new ODataProperty[]
                {
                    new ODataProperty { Name = "Id", Value = 1 },
                    new ODataProperty { Name = "Stream", Value = streamReferenceValue }
                };

                return new PayloadWriterTestDescriptor<ODataItem>(this.Settings, entry, testConfiguration =>
                    new AtomWriterTestExpectedResults(this.Settings.ExpectedResultSettings)
                    {
                        Xml = testCase.ExpectedXml == null ? null : testCase.ExpectedXml("http://docs.oasis-open.org/odata/ns/mediaresource/Stream", readLinkHref, "Stream", linkMediaType),
                        ExpectedException2 = testCase.ExpectedException == null ? null : testCase.ExpectedException("http://docs.oasis-open.org/odata/ns/mediaresource/Stream", readLinkHref),
                        FragmentExtractor = fragmentExtractor
                    });
            });

            var editLinkTestDescriptors = allTestCases.Select(testCase =>
            {
                ODataEntry entry = ObjectModelUtils.CreateDefaultEntryWithAtomMetadata();
                ODataStreamReferenceValue streamReferenceValue = new ODataStreamReferenceValue()
                {
                    ReadLink = new Uri(readLinkHref),
                    EditLink = new Uri(editLinkHref),
                    ContentType = linkMediaType,
                };

                AtomStreamReferenceMetadata streamReferenceMetadata = new AtomStreamReferenceMetadata()
                {
                    EditLink = testCase.LinkMetadata("http://docs.oasis-open.org/odata/ns/edit-media/Stream", editLinkHref)
                };

                streamReferenceValue.SetAnnotation<AtomStreamReferenceMetadata>(streamReferenceMetadata);
                entry.Properties = new ODataProperty[]
                {
                    new ODataProperty { Name = "Id", Value = 1 },
                    new ODataProperty { Name = "Stream", Value = streamReferenceValue }
                };

                return new PayloadWriterTestDescriptor<ODataItem>(this.Settings, entry, testConfiguration =>
                    new AtomWriterTestExpectedResults(this.Settings.ExpectedResultSettings)
                    {
                        Xml = testCase.ExpectedXml == null ? null : testCase.ExpectedXml("http://docs.oasis-open.org/odata/ns/edit-media/Stream", editLinkHref, "Stream", linkMediaType),
                        ExpectedException2 = testCase.ExpectedException == null ? null : testCase.ExpectedException("http://docs.oasis-open.org/odata/ns/edit-media/Stream", editLinkHref),
                        FragmentExtractor = fragmentExtractor
                    });
            });

            var testDescriptors = readLinkTestDescriptors.Concat(editLinkTestDescriptors);

            this.CombinatorialEngineProvider.RunCombinations(
                testDescriptors.PayloadCases(WriterPayloads.EntryPayloads),
                this.WriterTestConfigurationProvider.AtomFormatConfigurations
                    .Where(tc => !tc.IsRequest),
                (testDescriptor, testConfiguration) =>
                {
                    testConfiguration = testConfiguration.Clone();
                    testConfiguration.MessageWriterSettings.SetServiceDocumentUri(ServiceDocumentUri);

                    TestWriterUtils.WriteAndVerifyODataPayload(testDescriptor, testConfiguration, this.Assert, this.Logger);
                });
        }
        public void DefaultStreamEditLinkMetadataWriterTest()
        {
            Func<XElement, XElement> fragmentExtractor = (e) => e.Elements(TestAtomConstants.AtomXNamespace + "link").Last();

            // NOTE: no self-link test cases since the self link is represented as the <content> element and not customizable through link metadata
            var testDescriptors = linkMetadataTestCases.Select(testCase =>
            {
                ODataEntry entry = ObjectModelUtils.CreateDefaultEntryWithAtomMetadata();
                ODataStreamReferenceValue streamReferenceValue = new ODataStreamReferenceValue() 
                { 
                    ReadLink = new Uri(readLinkHref),
                    EditLink = new Uri(editLinkHref), 
                    ContentType = linkMediaType,
                };

                AtomStreamReferenceMetadata streamReferenceMetadata = new AtomStreamReferenceMetadata()
                {
                    EditLink = testCase.LinkMetadata("edit-media", editLinkHref)
                };

                streamReferenceValue.SetAnnotation<AtomStreamReferenceMetadata>(streamReferenceMetadata);
                entry.MediaResource = streamReferenceValue;

                return new PayloadWriterTestDescriptor<ODataItem>(this.Settings, entry, testConfiguration =>
                    new AtomWriterTestExpectedResults(this.Settings.ExpectedResultSettings)
                    {
                        Xml = testCase.ExpectedXml == null ? null : testCase.ExpectedXml("edit-media", editLinkHref, null, null),
                        ExpectedException2 = testCase.ExpectedException == null ? null : testCase.ExpectedException("edit-media", editLinkHref),
                        FragmentExtractor = fragmentExtractor
                    });
            });

            this.CombinatorialEngineProvider.RunCombinations(
                testDescriptors.PayloadCases(WriterPayloads.EntryPayloads),
                this.WriterTestConfigurationProvider.AtomFormatConfigurations,
                (testDescriptor, testConfiguration) =>
                {
                    testConfiguration = testConfiguration.Clone();
                    testConfiguration.MessageWriterSettings.SetServiceDocumentUri(ServiceDocumentUri);

                    TestWriterUtils.WriteAndVerifyODataPayload(testDescriptor, testConfiguration, this.Assert, this.Logger);
                });
        }