File: System\Data\Services\Serializers\SyndicationDeserializer.cs
Project: ndp\fx\src\DataWeb\Server\System.Data.Services.csproj (System.Data.Services)
//---------------------------------------------------------------------
// <copyright file="SyndicationDeserializer.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      Provides a deserializer for atom content structured as 
//      syndication items or feeds.
// </summary>
//
// @owner Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Services.Serializers
{
    #region Namespaces.
 
    using System;
    using System.Collections.Generic;
    using System.Data.Services.Common;
    using System.Data.Services.Providers;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.ServiceModel.Syndication;
    using System.Text;
    using System.Xml;
 
    #endregion Namespaces.
 
    /// <summary>Provides a deserializer for structured content.</summary>
    internal class SyndicationDeserializer : Deserializer
    {
        /// <summary>Factory for syndication formatting objects.</summary>
        private readonly SyndicationFormatterFactory factory;
 
        /// <summary>reader to read xml from the request stream</summary>
        private readonly XmlReader xmlReader;
 
        /// <summary>Initializes a new <see cref="SyndicationDeserializer"/> for the specified stream.</summary>
        /// <param name="stream">Input stream reader from which ATOM content must be read.</param>
        /// <param name="encoding">Encoding to use for the stream (null to auto-discover).</param>
        /// <param name="dataService">Data service for which the deserializer will act.</param>
        /// <param name="update">indicates whether this is a update operation or not</param>
        /// <param name="factory">Factory for formatter objects.</param>
        /// <param name="tracker">Tracker to use for modifications.</param>
        internal SyndicationDeserializer(Stream stream, Encoding encoding, IDataService dataService, bool update, SyndicationFormatterFactory factory, UpdateTracker tracker)
            : base(update, dataService, tracker)
        {
            Debug.Assert(stream != null, "stream != null");
            Debug.Assert(factory != null, "factory != null");
 
            this.factory = factory;
            this.xmlReader = factory.CreateReader(stream, encoding);
        }
 
        /// <summary>
        /// Indicates the various form of data in the inline xml element
        /// </summary>
        private enum LinkContent
        {
            /// <summary>If the link element didn't not contain an inline element at all.</summary>
            NoInlineElementSpecified,
 
            /// <summary>If the link element contained an empty inline element.</summary>
            EmptyInlineElementSpecified,
 
            /// <summary>If the inline element under the link element contained some data.</summary>
            InlineElementContainsData
        }
 
        /// <summary>returns the content format for the deserializer</summary>
        protected override ContentFormat ContentFormat
        {
            get
            {
                return ContentFormat.Atom;
            }
        }
 
        /// <summary>
        /// Assumes the payload to represent a single object and processes accordingly
        /// </summary>
        /// <param name="segmentInfo">info about the object being created</param>
        /// <returns>the newly formed object that the payload represents</returns>
        protected override object CreateSingleObject(SegmentInfo segmentInfo)
        {
            SyndicationItem item = ReadSyndicationItem(this.factory.CreateSyndicationItemFormatter(), this.xmlReader);
            return this.CreateObject(segmentInfo, true /*topLevel*/, item);
        }
 
        /// <summary>Provides an opportunity to clean-up resources.</summary>
        /// <param name="disposing">
        /// Whether the call is being made from an explicit call to 
        /// IDisposable.Dispose() rather than through the finalizer.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            if (disposing)
            {
                this.xmlReader.Close();
            }
        }
 
        /// <summary>
        /// Get the resource referred by the uri in the payload
        /// </summary>
        /// <returns>resource referred by the uri in the payload.</returns>
        protected override string GetLinkUriFromPayload()
        {
            throw Error.NotImplemented();
        }
 
        /// <summary>Checks whether the specified item has a payload.</summary>
        /// <param name='item'>Item to check.</param>
        /// <returns>true if the item has content or links specified; false otherwise.</returns>
        private static bool HasContent(SyndicationItem item)
        {
            return item.Content != null || item.Links.Count > 0;
        }
 
        /// <summary>Gets the text for the type annotated on the specified <paramref name='item' />.</summary>
        /// <param name='item'>Item to read type from.</param>
        /// <returns>The text for the type annotated on the specified item; null if none is set.</returns>
        private static string SyndicationItemGetType(SyndicationItem item)
        {
            Debug.Assert(item != null, "item != null");
            SyndicationCategory category = item.Categories.Where(c => c.Scheme == XmlConstants.DataWebSchemeNamespace).FirstOrDefault();
            return (category == null) ? null : category.Name;
        }
 
        /// <summary>Reads a SyndicationFeed object from the specified XmlReader.</summary>
        /// <param name='formatter'>Formatter to use when reading content.</param>
        /// <param name='reader'>Read to read feed from.</param>
        /// <returns>A new SyndicationFeed instance.</returns>
        private static SyndicationFeed ReadSyndicationFeed(SyndicationFeedFormatter formatter, XmlReader reader)
        {
            Debug.Assert(formatter != null, "formatter != null");
            Debug.Assert(reader != null, "reader != null");
 
            try
            {
                formatter.ReadFrom(reader);
            }
            catch (XmlException exception)
            {
                throw DataServiceException.CreateBadRequestError(Strings.Syndication_ErrorReadingFeed(exception.Message), exception);
            }
 
            Debug.Assert(formatter.Feed != null, "formatter.Feed != null");
            return formatter.Feed;
        }
 
        /// <summary>Reads a SyndicationItem object from the specified XmlReader.</summary>
        /// <param name='formatter'>Formatter to use when reading content.</param>
        /// <param name='reader'>Read to read feed from.</param>
        /// <returns>A new SyndicationItem instance.</returns>
        private static SyndicationItem ReadSyndicationItem(SyndicationItemFormatter formatter, XmlReader reader)
        {
            Debug.Assert(formatter != null, "formatter != null");
            Debug.Assert(reader != null, "reader != null");
 
            try
            {
                formatter.ReadFrom(reader);
            }
            catch (XmlException exception)
            {
                throw DataServiceException.CreateBadRequestError(Strings.Syndication_ErrorReadingEntry(exception.Message), exception);
            }
 
            Debug.Assert(formatter.Item != null, "formatter.Item != null");
            return formatter.Item;
        }
 
        /// <summary>
        /// Read the link media type and validate for non open property types
        /// </summary>
        /// <param name="mediaType">media type as specified on the link element.</param>
        /// <param name="property">property which the link represents.</param>
        /// <returns>returns the type parameters specified in the media link.</returns>
        private static string ValidateTypeParameterForNonOpenTypeProperties(string mediaType, ResourceProperty property)
        {
            string typeParameterValue = null;
            if (!String.IsNullOrEmpty(mediaType))
            {
                string mime;
                Encoding encoding;
                KeyValuePair<string, string>[] contentTypeParameters = HttpProcessUtility.ReadContentType(mediaType, out mime, out encoding);
                if (mime != XmlConstants.MimeApplicationAtom)
                {
                    throw DataServiceException.CreateBadRequestError(Strings.BadRequest_MimeTypeMustBeApplicationAtom(mime, XmlConstants.MimeApplicationAtom));
                }
 
                // If the type parameter is specified, make sure its correct. We do the validation for known properties here
                // and for open-properties, the validation is done if the link is expanded. Otherwise, there is no good way of
                // doing the validation.
                typeParameterValue = HttpProcessUtility.GetParameterValue(contentTypeParameters, XmlConstants.AtomTypeAttributeName);
                if (!String.IsNullOrEmpty(typeParameterValue) && property != null)
                {
                    if (property.Kind == ResourcePropertyKind.ResourceReference)
                    {
                        if (typeParameterValue != XmlConstants.AtomEntryElementName)
                        {
                            throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidTypeParameterSpecifiedInMimeType(typeParameterValue, XmlConstants.AtomEntryElementName));
                        }
                    }
                    else if (typeParameterValue != XmlConstants.AtomFeedElementName)
                    {
                        throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidTypeParameterSpecifiedInMimeType(typeParameterValue, XmlConstants.AtomFeedElementName));
                    }
                }
            }
 
            return typeParameterValue;
        }
 
        /// <summary>
        /// Gets the XmlReader for the m:properties element under the atom:entry element
        /// </summary>
        /// <param name="item">item to read from</param>
        /// <returns>XmlReader for the m:properties element if found, null otherwise.</returns>
        private static XmlReader GetPropertiesReaderFromEntry(SyndicationItem item)
        {
            SyndicationElementExtension properties = item.ElementExtensions.Where(p => p.OuterName == XmlConstants.AtomPropertiesElementName && p.OuterNamespace == XmlConstants.DataWebMetadataNamespace).FirstOrDefault();
            if (properties != null)
            {
                return properties.GetReader();
            }
 
            return null;
        }
 
        /// <summary>
        /// Gets the XmlReader for the m:properties element under the atom:content element
        /// </summary>
        /// <param name="item">item to read from</param>
        /// <returns>XmlReader for the m:properties element if found, null otherwise.</returns>
        private static XmlReader GetPropertiesReaderFromContent(SyndicationItem item)
        {
            XmlSyndicationContent itemContent = item.Content as XmlSyndicationContent;
            XmlReader reader = null;
            if (itemContent != null)
            {
                string contentType = itemContent.Type;
                if (!WebUtil.CompareMimeType(contentType, XmlConstants.MimeApplicationXml))
                {
                    throw DataServiceException.CreateBadRequestError(
                        Strings.Syndication_EntryContentTypeUnsupported(contentType));
                }
 
                bool shouldDispose = false;
                try
                {
                    reader = itemContent.GetReaderAtContent();
                    WebUtil.XmlReaderEnsureElement(reader);
                    Debug.Assert(
                        reader.NodeType == XmlNodeType.Element,
                        reader.NodeType.ToString() + " == XmlNodeType.Element -- otherwise XmlSyndicationContent didn't see a 'content' tag");
 
                    reader.ReadStartElement(XmlConstants.AtomContentElementName, XmlConstants.AtomNamespace);
                    if (!reader.IsStartElement(XmlConstants.AtomPropertiesElementName, XmlConstants.DataWebMetadataNamespace))
                    {
                        shouldDispose = true;
                    }
                }
                catch
                {
                    shouldDispose = true;
                    throw;
                }
                finally
                {
                    if (shouldDispose)
                    {
                        WebUtil.Dispose(reader);
                        reader = null;
                    }
                }
            }
 
            return reader;
        }
 
        /// <summary>Applies the properties in the plain XML content to the specified resource.</summary>
        /// <param name="item">item to read from.</param>
        /// <param name='resourceType'>Type of resource whose values are being set.</param>
        /// <param name="propertiesApplied">Properties that have been applied to the <paramref name="resource"/></param>
        /// <param name='resource'>Target resource.</param>
        private void ApplyProperties(SyndicationItem item, ResourceType resourceType, EpmContentDeSerializer.EpmAppliedPropertyInfo propertiesApplied, object resource)
        {
            Debug.Assert(item != null, "item != null");
            Debug.Assert(resourceType != null, "resourceType != null");
            Debug.Assert(propertiesApplied != null, "propertiesApplied != null");
            Debug.Assert(resource != null, "resource != null");
 
            using (XmlReader propertiesFromEntry = GetPropertiesReaderFromEntry(item))
            using (XmlReader propertiesFromContent = GetPropertiesReaderFromContent(item))
            {
                XmlReader reader = null;
 
                if (resourceType.IsMediaLinkEntry)
                {
                    if (propertiesFromContent != null)
                    {
                        // The server is expecting a MLE payload, however we found a <m:properties> element under the <atom:content> element.
                        // The client must assumed the resource type is a non-MLE type and serialized as such.
                        //
                        // We must throw here otherwise the client would falsely assume all the properties are correctly deserialized on
                        // the server where as in fact the properties element is under the wrong node.
                        throw DataServiceException.CreateBadRequestError(Strings.DataServiceException_GeneralError);
                    }
 
                    reader = propertiesFromEntry;
                }
                else
                {
                    if (propertiesFromEntry != null)
                    {
                        // The server is expecting a non-MLE payload, however we found a <m:properties> element under the <entry> element.
                        // The client must assumed the resource type is a MLE type and serialized as such.
                        //
                        // We must throw here otherwise the client would falsely assume all the properties are correctly deserialized on
                        // the server where as in fact the properties element is under the wrong node.
                        throw DataServiceException.CreateBadRequestError(Strings.DataServiceException_GeneralError);
                    }
 
                    reader = propertiesFromContent;
                }
 
                if (reader != null)
                {
                    reader.ReadStartElement(XmlConstants.AtomPropertiesElementName, XmlConstants.DataWebMetadataNamespace);
                    PlainXmlDeserializer.ApplyContent(this, reader, resourceType, resource, propertiesApplied, this.MaxObjectCount);
                }
            }
        }
 
        /// <summary>Reads the current object from the <paramref name="item"/>.</summary>
        /// <param name="segmentInfo">segmentinfo containing information about the current element that is getting processes</param>
        /// <param name="topLevel">true if the element currently pointed by the xml reader refers to a top level element</param>
        /// <param name="item">Item to read from.</param>
        /// <returns>returns the clr object with the data populated</returns>
        private object CreateObject(SegmentInfo segmentInfo, bool topLevel, SyndicationItem item)
        {
            Debug.Assert(item != null, "item != null");
            Debug.Assert(topLevel || !this.Update, "deep updates not supported");
 
            this.RecurseEnter();
            object result;
 
            // update the object count everytime you encounter a new resource
            this.CheckAndIncrementObjectCount();
 
            // Process the type annotation.
            ResourceType currentResourceType = this.GetResourceType(item, segmentInfo.TargetResourceType);
            if (currentResourceType.ResourceTypeKind != ResourceTypeKind.EntityType)
            {
                throw DataServiceException.CreateBadRequestError(Strings.BadRequest_OnlyEntityTypesMustBeSpecifiedInEntryElement(currentResourceType.FullName));
            }
 
            // We have the actual type info from the payload. Update the request/response DSV if any property is FF mapped with KeepInContent=false.
            this.UpdateAndCheckEpmRequestResponseDSV(currentResourceType, topLevel);
 
            // Get a resource cookie from the provider.
            ResourceSetWrapper container;
            if (segmentInfo.TargetKind == RequestTargetKind.OpenProperty)
            {
                // Open navigation properties are not supported on OpenTypes.
                throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(segmentInfo.Identifier));
            }
            else
            {
                Debug.Assert(segmentInfo.TargetKind == RequestTargetKind.Resource, "segmentInfo.TargetKind == RequestTargetKind.Resource");
                container = segmentInfo.TargetContainer;
            }
 
            DataServiceHostWrapper host = this.Service.OperationContext.Host;
            if (this.Update)
            {
                Debug.Assert(currentResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "only expecting entity types");
 
                // Only verify ETag if there is going to be some update applied (that's the idea)
                // In reality:
                //   - for normal entities (V1 compatible) - don't check ETags if there is no content element. (Same as in V1)
                //   - for V2 stuff - check ETags always as we can't tell if there's going to be something modified or not
                //       with EPM properties can be anywhere in the payload and thus even without content there still can be updates
                //       with MLE the properties are not in the content element but in their own element
                // It's hard to recognize if there's going to be update up front and so this below is an approximation
                //   which seems to be good enough. Note that if we add new ways of handling properties in the content
                //   the condition below might need to change.
                bool verifyETag = 
                    topLevel && 
                    (HasContent(item) || currentResourceType.HasEntityPropertyMappings || currentResourceType.IsMediaLinkEntry);
                bool replaceResource = topLevel && host.AstoriaHttpVerb == AstoriaVerbs.PUT;
 
                // if its a top level resource, then it cannot be null
                result = this.GetObjectFromSegmentInfo(currentResourceType, segmentInfo, verifyETag, topLevel /*checkForNull*/, replaceResource);
                if (this.Tracker != null)
                {
                    this.Tracker.TrackAction(result, container, UpdateOperations.Change);
                }
            }
            else
            {
                if (segmentInfo.TargetKind == RequestTargetKind.Resource)
                {
                    DataServiceConfiguration.CheckResourceRights(segmentInfo.TargetContainer, EntitySetRights.WriteAppend);
                }
 
                result = this.Updatable.CreateResource(container.Name, currentResourceType.FullName);
                if (this.Tracker != null)
                {
                    this.Tracker.TrackAction(result, container, UpdateOperations.Add);
                }
            }
 
            // Process the content in the entry.
            EpmContentDeSerializer.EpmAppliedPropertyInfo propertiesApplied = new EpmContentDeSerializer.EpmAppliedPropertyInfo();
            this.ApplyProperties(item, currentResourceType, propertiesApplied, result);
 
            // Perform application of epm properties here
            if (currentResourceType.HasEntityPropertyMappings)
            {
                new EpmContentDeSerializer(currentResourceType, result).DeSerialize(
                        item, 
                        new EpmContentDeSerializer.EpmContentDeserializerState { IsUpdateOperation = this.Update, Updatable = this.Updatable, Service = this.Service, PropertiesApplied = propertiesApplied });
            }
 
            // Process the links in the entry.
            foreach (SyndicationLink link in item.Links)
            {
                string navigationPropertyName = UriUtil.GetNameFromAtomLinkRelationAttribute(link.RelationshipType);
 
                if (null == navigationPropertyName)
                {
                    continue;
                }
 
                Deserializer.CheckForBindingInPutOperations(host.AstoriaHttpVerb);
                Debug.Assert(segmentInfo.TargetContainer != null, "segmentInfo.TargetContainer != null");
                this.ApplyLink(link, segmentInfo.TargetContainer, currentResourceType, result, navigationPropertyName);
            }
 
            this.RecurseLeave();
            return result;
        }
 
        /// <summary>Applies the information from a link to the specified resource.</summary>
        /// <param name='link'>LinkDescriptor with information to apply.</param>
        /// <param name="resourceSet">Set for the target resource.</param>
        /// <param name='resourceType'>Type for the target resource.</param>
        /// <param name='resource'>Target resource to which information will be applied.</param>
        /// <param name="propertyName">Name of the property that this link represents.</param>
        private void ApplyLink(SyndicationLink link, ResourceSetWrapper resourceSet, ResourceType resourceType, object resource, string propertyName)
        {
            Debug.Assert(link != null, "link != null");
            Debug.Assert(resourceType != null, "resourceType != null");
            Debug.Assert(resource != null, "resource != null");
 
            ResourceProperty property = resourceType.TryResolvePropertyName(propertyName);
            if (property == null)
            {
                // Open navigation properties are not supported on OpenTypes
                throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(propertyName));
            }
 
            if (property.TypeKind != ResourceTypeKind.EntityType)
            {
                throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidNavigationPropertyName(propertyName, resourceType.FullName));
            }
 
            string typeParameterValue = ValidateTypeParameterForNonOpenTypeProperties(link.MediaType, property);
            LinkContent linkContent = this.HandleLinkContent(link, resource, resourceSet, resourceType, property, typeParameterValue, propertyName);
 
            #region Handle bind/unbind operation
            // If the href was specified empty or an empty inline element was specified, then we will set the 
            // reference to null - this helps in overrriding if there was a default non-value for this property
            // else if only link element was specified, and then href points to a single result, then we will
            // perform a bind operation
            if ((linkContent == LinkContent.NoInlineElementSpecified && link.Uri != null && String.IsNullOrEmpty(link.Uri.OriginalString)) ||
                linkContent == LinkContent.EmptyInlineElementSpecified)
            {
                // update the object count when you are performing a bind operation
                this.CheckAndIncrementObjectCount();
                if (property != null && property.Kind == ResourcePropertyKind.ResourceSetReference)
                {
                    throw DataServiceException.CreateBadRequestError(Strings.BadRequest_CannotSetCollectionsToNull(propertyName));
                }
 
                // For open properties, we will assume that this is a reference property and set it to null
                this.Updatable.SetReference(resource, propertyName, null);
            }
            else if (linkContent == LinkContent.NoInlineElementSpecified && link.Uri != null && !String.IsNullOrEmpty(link.Uri.OriginalString))
            {
                // update the object count when you are performing a bind operation
                this.CheckAndIncrementObjectCount();
 
                // If the link points to a reference navigation property, then update the link
                Uri referencedUri = RequestUriProcessor.GetAbsoluteUriFromReference(link.Uri.OriginalString, this.Service.OperationContext);
                RequestDescription description = RequestUriProcessor.ProcessRequestUri(referencedUri, this.Service);
                if (!description.IsSingleResult)
                {
                    if (property != null && property.Kind == ResourcePropertyKind.ResourceReference)
                    {
                        throw DataServiceException.CreateBadRequestError(Strings.BadRequest_LinkHrefMustReferToSingleResource(propertyName));
                    }
 
                    return;
                }
 
                // no need to check for null. For collection properties, they can never be null and that
                // check has been added below. For reference properties, if they are null, it means unbind
                // and hence no need to check for null.
                // Get the resource
                object targetResource = this.Service.GetResource(description, description.SegmentInfos.Length - 1, null);
                if (property.Kind == ResourcePropertyKind.ResourceReference)
                {
                    this.Updatable.SetReference(resource, propertyName, targetResource);
                }
                else
                {
                    WebUtil.CheckResourceExists(targetResource != null, description.LastSegmentInfo.Identifier);
                    this.Updatable.AddReferenceToCollection(resource, propertyName, targetResource);
                }
            }
            #endregion Handle bind/unbind operation
        }
 
        /// <summary>Gets the type attribute and resolves the type.</summary>
        /// <param name="item">Item from which type attribute needs to be read</param>
        /// <param name="expectedType">Expected base type for the item.</param>
        /// <returns>Resolved type.</returns>
        private ResourceType GetResourceType(SyndicationItem item, ResourceType expectedType)
        {
            Debug.Assert(item != null, "item != null");
            string typeName = SyndicationItemGetType(item);
            ResourceType resourceType;
 
            // If the type is not specified in the payload, we assume the type to be the expected type
            if (String.IsNullOrEmpty(typeName))
            {
                // check if the expected type takes part in inheritance
                resourceType = expectedType;
                if (this.Service.Provider.HasDerivedTypes(resourceType))
                {
                    throw DataServiceException.CreateBadRequestError(Strings.BadRequest_TypeInformationMustBeSpecifiedForInhertiance);
                }
            }
            else
            {
                // Otherwise, try and resolve the name specified in the payload
                resourceType = this.Service.Provider.TryResolveResourceType(typeName);
                if (resourceType == null)
                {
                    throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidTypeName(typeName));
                }
            }
 
            return resourceType;
        }
 
        /// <summary>
        /// Handle the contents under the link element
        /// </summary>
        /// <param name="link">syndication link element</param>
        /// <param name="parentResource">parent resource which contains the link.</param>
        /// <param name="parentResourceSet">resource set of the parent resource</param>
        /// <param name="parentResourceType">resource type of the parent resource</param>
        /// <param name="property">property representing the link.</param>
        /// <param name="typeParameterValue">type parameter value as specified in the type attribute.</param>
        /// <param name="propertyName">name of the property that this link represents.</param>
        /// <returns>returns whether there are child elements under link element.</returns>
        private LinkContent HandleLinkContent(
            SyndicationLink link,
            object parentResource,
            ResourceSetWrapper parentResourceSet,
            ResourceType parentResourceType,
            ResourceProperty property,
            string typeParameterValue,
            string propertyName)
        {
            Debug.Assert(parentResource != null, "parent resource cannot be null");
            Debug.Assert(property != null, "property != null");
            Debug.Assert(link != null, "link != null");
 
            LinkContent linkContent = LinkContent.NoInlineElementSpecified;
            foreach (var e in link.ElementExtensions)
            {
                // link can contain other elements apart from the inline elements.
                if (e.OuterNamespace != XmlConstants.DataWebMetadataNamespace ||
                    e.OuterName != XmlConstants.AtomInlineElementName)
                {
                    continue;
                }
 
                // Deep payload cannot be specified for update
                if (this.Update)
                {
                    throw DataServiceException.CreateBadRequestError(Strings.BadRequest_DeepUpdateNotSupported);
                }
 
                linkContent = LinkContent.EmptyInlineElementSpecified;
                using (XmlReader linkReader = e.GetReader())
                {
                    while (linkReader.Read())
                    {
                        if (linkReader.NodeType == XmlNodeType.Element)
                        {
                            string elementName = linkReader.LocalName;
                            string namespaceUri = linkReader.NamespaceURI;
                            if (namespaceUri != XmlConstants.AtomNamespace)
                            {
                                throw DataServiceException.CreateBadRequestError(
                                    Strings.BadRequest_InlineElementMustContainValidElement(
                                        elementName,
                                        XmlConstants.AtomInlineElementName,
                                        XmlConstants.AtomFeedElementName,
                                        XmlConstants.AtomEntryElementName));
                            }
 
                            ResourceSetWrapper targetSet = this.Service.Provider.GetContainer(parentResourceSet, parentResourceType, property);
                            if (targetSet == null)
                            {
                                throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidPropertyNameSpecified(propertyName, parentResourceType.FullName));
                            }
 
                            // FeatureVersion needs to be 2.0 if any of the property in the types contained in the resource set has KeepInContent false
                            this.RequestDescription.UpdateAndCheckEpmFeatureVersion(targetSet, this.Service);
 
                            linkContent = LinkContent.InlineElementContainsData;
                            if (elementName == XmlConstants.AtomEntryElementName)
                            {
                                if (property.Kind != ResourcePropertyKind.ResourceReference)
                                {
                                    throw DataServiceException.CreateBadRequestError(Strings.Syndication_EntryElementForReferenceProperties(e.OuterName, propertyName));
                                }
 
                                // Make sure if the media type is specified. If its specified, it should better be link
                                SyndicationItem propertyItem;
                                propertyItem = ReadSyndicationItem(this.factory.CreateSyndicationItemFormatter(), linkReader);
 
                                SegmentInfo propertySegment = CreateSegment(property, propertyName, targetSet, true /* singleResult */);
                                Debug.Assert(propertySegment.TargetKind != RequestTargetKind.OpenProperty, "Open navigation properties are not supported on OpenTypes.");
 
                                object propertyValue = this.CreateObject(propertySegment, false /* topLevel */, propertyItem);
                                this.Updatable.SetReference(parentResource, propertyName, propertyValue);
                            }
                            else if (elementName == XmlConstants.AtomFeedElementName)
                            {
                                    if (property.Kind != ResourcePropertyKind.ResourceSetReference)
                                    {
                                        throw DataServiceException.CreateBadRequestError(Strings.Syndication_FeedElementForCollections(e.OuterName, propertyName));
                                    }
 
                                SyndicationFeed propertyFeed;
                                propertyFeed = ReadSyndicationFeed(this.factory.CreateSyndicationFeedFormatter(), linkReader);
 
                                SegmentInfo propertySegment = CreateSegment(property, propertyName, targetSet, false /* singleResult */);
                                Debug.Assert(propertySegment.TargetKind != RequestTargetKind.OpenProperty, "Open navigation properties are not supported on OpenTypes.");
 
                                foreach (SyndicationItem item in propertyFeed.Items)
                                {
                                    object propertyValue = this.CreateObject(propertySegment, false /* topLevel */, item);
                                    if (propertyValue == null)
                                    {
                                        if (propertySegment.ProjectedProperty != null &&
                                            propertySegment.ProjectedProperty.Kind == ResourcePropertyKind.ResourceSetReference)
                                        {
                                            throw DataServiceException.CreateBadRequestError(
                                                Strings.BadRequest_CannotSetCollectionsToNull(propertyName));
                                        }
                                    }
 
                                    Debug.Assert(
                                            propertySegment.TargetSource == RequestTargetSource.Property &&
                                            propertySegment.TargetKind == RequestTargetKind.Resource &&
                                            propertySegment.SingleResult == false,
                                            "Must be navigation set property.");
 
                                    this.Updatable.AddReferenceToCollection(parentResource, propertyName, propertyValue);
                                }
                            }
                            else
                            {
                                throw DataServiceException.CreateBadRequestError(
                                    Strings.BadRequest_InlineElementMustContainValidElement(
                                        elementName,
                                        XmlConstants.AtomInlineElementName,
                                        XmlConstants.AtomFeedElementName,
                                        XmlConstants.AtomEntryElementName));
                            }
                        }
                    }
                }
            }
 
            return linkContent;
        }
    }
}