Ejemplo n.º 1
0
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="entryState">The reader entry state to use for the entry to which the EPM is applied.</param>
 /// <param name="inputContext">The input context currently in use.</param>
 protected EpmReader(
     IODataAtomReaderEntryState entryState,
     ODataAtomInputContext inputContext)
 {
     this.entryState = entryState;
     this.atomInputContext = inputContext;
 }
Ejemplo n.º 2
0
 internal bool TryReadExtensionElementInEntryContent(IODataAtomReaderEntryState entryState)
 {
     ODataEntityPropertyMappingCache cachedEpm = entryState.CachedEpm;
     if (cachedEpm == null)
     {
         return false;
     }
     EpmTargetPathSegment nonSyndicationRoot = cachedEpm.EpmTargetTree.NonSyndicationRoot;
     return this.TryReadCustomEpmElement(entryState, nonSyndicationRoot);
 }
Ejemplo n.º 3
0
 private void ReadCustomEpmAttribute(IODataAtomReaderEntryState entryState, EpmTargetPathSegment epmTargetPathSegmentForElement)
 {
     string localName = base.XmlReader.LocalName;
     string namespaceUri = base.XmlReader.NamespaceURI;
     EpmTargetPathSegment segment = epmTargetPathSegmentForElement.SubSegments.FirstOrDefault<EpmTargetPathSegment>(x => (x.IsAttribute && (string.CompareOrdinal(x.AttributeName, localName) == 0)) && (string.CompareOrdinal(x.SegmentNamespaceUri, namespaceUri) == 0));
     if ((segment != null) && !entryState.EpmCustomReaderValueCache.Contains(segment.EpmInfo))
     {
         entryState.EpmCustomReaderValueCache.Add(segment.EpmInfo, base.XmlReader.Value);
     }
 }
 internal static void EnsureMediaResource(IODataAtomReaderEntryState entryState, bool validateMLEPresence)
 {
     if (validateMLEPresence)
     {
         entryState.MediaLinkEntry = true;
     }
     ODataEntry entry = entryState.Entry;
     if (entry.MediaResource == null)
     {
         entry.MediaResource = new ODataStreamReferenceValue();
     }
 }
Ejemplo n.º 5
0
        /// <summary>
        /// Reads the custom EPM for an entry.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry to which the EPM is applied.</param>
        /// <param name="inputContext">The input context currently in use.</param>
        internal static void ReadEntryEpm(
            IODataAtomReaderEntryState entryState,
            ODataAtomInputContext inputContext)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(entryState != null, "entryState != null");
            Debug.Assert(entryState.CachedEpm != null, "To read entry EPM, the entity type must have an EPM annotation.");
            Debug.Assert(entryState.EpmCustomReaderValueCache != null, "To read custom entry EPM, the entry must have the custom EPM reader value cache.");
            Debug.Assert(inputContext != null, "inputContext != null");

            EpmCustomReader epmCustomReader = new EpmCustomReader(entryState, inputContext);
            epmCustomReader.ReadEntryEpm();
        }
Ejemplo n.º 6
0
 private bool TryReadCustomEpmElement(IODataAtomReaderEntryState entryState, EpmTargetPathSegment epmTargetPathSegment)
 {
     string localName = base.XmlReader.LocalName;
     string namespaceUri = base.XmlReader.NamespaceURI;
     EpmTargetPathSegment epmTargetPathSegmentForElement = epmTargetPathSegment.SubSegments.FirstOrDefault<EpmTargetPathSegment>(segment => (!segment.IsAttribute && (string.CompareOrdinal(segment.SegmentName, localName) == 0)) && (string.CompareOrdinal(segment.SegmentNamespaceUri, namespaceUri) == 0));
     if ((epmTargetPathSegmentForElement != null) && (!epmTargetPathSegmentForElement.HasContent || !entryState.EpmCustomReaderValueCache.Contains(epmTargetPathSegmentForElement.EpmInfo)))
     {
         while (base.XmlReader.MoveToNextAttribute())
         {
             this.ReadCustomEpmAttribute(entryState, epmTargetPathSegmentForElement);
         }
         base.XmlReader.MoveToElement();
         if (epmTargetPathSegmentForElement.HasContent)
         {
             string str = base.ReadElementStringValue();
             entryState.EpmCustomReaderValueCache.Add(epmTargetPathSegmentForElement.EpmInfo, str);
             goto Label_0115;
         }
         if (!base.XmlReader.IsEmptyElement)
         {
             base.XmlReader.Read();
             while (base.XmlReader.NodeType != XmlNodeType.EndElement)
             {
                 switch (base.XmlReader.NodeType)
                 {
                     case XmlNodeType.Element:
                     {
                         if (!this.TryReadCustomEpmElement(entryState, epmTargetPathSegmentForElement))
                         {
                             base.XmlReader.Skip();
                         }
                         continue;
                     }
                     case XmlNodeType.EndElement:
                     {
                         continue;
                     }
                 }
                 base.XmlReader.Skip();
             }
         }
     }
     else
     {
         return false;
     }
     base.XmlReader.Read();
 Label_0115:
     return true;
 }
        /// <summary>
        /// Reads an extension element in non-ATOM namespace in the content of the entry element.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <returns>true if a mapping for the current custom element was found and the element was read; otherwise false.</returns>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element - the element in non-ATOM namespace to read.
        /// Post-Condition: Any                 - the node after the extension element which was read.
        /// </remarks>
        internal bool TryReadExtensionElementInEntryContent(IODataAtomReaderEntryState entryState)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(entryState != null, "entryState != null");
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(this.XmlReader.NamespaceURI != AtomConstants.AtomNamespace, "Only elements in non-ATOM namespace can be read by this method.");

            ODataEntityPropertyMappingCache cachedEpm = entryState.CachedEpm;
            if (cachedEpm == null)
            {
                return false;
            }

            EpmTargetPathSegment epmTargetPathSegment = cachedEpm.EpmTargetTree.NonSyndicationRoot;
            return this.TryReadCustomEpmElement(entryState, epmTargetPathSegment);
        }
 internal void ReadAtomCategoryElementInEntryContent(IODataAtomReaderEntryState entryState)
 {
     ODataEntityPropertyMappingCache cachedEpm = entryState.CachedEpm;
     EpmTargetPathSegment syndicationRoot = null;
     if (cachedEpm != null)
     {
         syndicationRoot = cachedEpm.EpmTargetTree.SyndicationRoot;
     }
     if (syndicationRoot == null)
     {
     }
     bool flag = syndicationRoot.SubSegments.Any<EpmTargetPathSegment>();
     if (base.ReadAtomMetadata || flag)
     {
         AtomCategoryMetadata categoryMetadata = this.ReadAtomCategoryElement();
         AtomMetadataReaderUtils.AddCategoryToEntryMetadata(entryState.AtomEntryMetadata, categoryMetadata);
     }
     else
     {
         base.XmlReader.Skip();
     }
 }
Ejemplo n.º 9
0
 internal static void ReadEntryEpm(IODataAtomReaderEntryState entryState, ODataAtomInputContext inputContext)
 {
     new EpmCustomReader(entryState, inputContext).ReadEntryEpm();
 }
        /// <summary>
        /// Reads the atom:id element in the atom:entry element.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <remarks>
        /// Pre-Condition:   XmlNodeType.Element atom:id - The atom:id element to read.
        /// Post-Condition:  Any                         - The node after the atom:id element.
        /// </remarks>
        private void ReadAtomIdElementInEntry(IODataAtomReaderEntryState entryState)
        {
            Debug.Assert(entryState != null, "entryState != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomIdElementName,
                "The XML reader must be on the atom:id element for this method to work.");

            // The default behavior is to disallow duplicates of entry/id element defined in ODATA spec.
            this.ValidateDuplicateElement(entryState.HasId && this.AtomInputContext.UseDefaultFormatBehavior);

            // [Client-ODataLib-Integration] Client reads atom:id content using the semantics of XElement.Value, which is against the spec
            //     We decided to break the client and implement the correct behavior, the below method will read all text nodes and concatenate them.
            //     It will ignore all insignificant whitespace nodes, comments and PIs. It will fail on any elements.
            // [Astoria-ODataLib-Integration] Handling of invalid input data which WCF DS Server V2 ignores
            //     WCF DS V2 server requires the id value to be a simple content, so the ReadElementValue is actually more relaxed
            //     (it allows comments and such, which WCF DS V2 didn't).
            string idValue = this.XmlReader.ReadElementValue();

            // We have to support entries without IDs to enable OData insert scenarios.
            // This is a deviation from the ATOM spec where the ID is required and must not be null.
            // To enable chaining of entries without IDs we report empty IDs as 'null' (which
            // our writers will accept).
            if (idValue != null && idValue.Length == 0)
            {
                entryState.Entry.Id = null;
            }
            else
            {
                if (idValue != null && IsTransientId(idValue))
                {
                    entryState.Entry.IsTransient = true;
                }
                else
                {
                    entryState.Entry.Id = UriUtils.CreateUriAsEntryOrFeedId(idValue, UriKind.Absolute);
                }
            }

            entryState.HasId = true;
        }
        /// <summary>
        /// Reads the atom:content element.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <remarks>
        /// Pre-Condition:   XmlNodeType.Element atom:content  - The atom:content element to read.
        /// Post-Condition:  Any                               - The node after the atom:content element.
        /// </remarks>
        private void ReadAtomContentElement(IODataAtomReaderEntryState entryState)
        {
            Debug.Assert(entryState != null, "entryState != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomContentElementName,
                "The XML reader must be on the atom:content element for this method to work.");

            this.ValidateDuplicateElement(entryState.HasContent && this.AtomInputContext.UseDefaultFormatBehavior);
            
            if (this.AtomInputContext.UseClientFormatBehavior)
            {
                entryState.HasProperties = false;
            }

            // atom:content
            // Read the attributes - we're interested in type and src
            string contentType;
            string contentSource;
            this.ReadAtomContentAttributes(out contentType, out contentSource);

            if (contentSource != null)
            {
                // atom:content/@src means this is an MLE
                ODataEntry entry = entryState.Entry;
                EnsureMediaResource(entryState, /*validateMLEPresence*/ true);

                if (!this.AtomInputContext.UseServerFormatBehavior)
                {
                    entry.MediaResource.ReadLink = this.ProcessUriFromPayload(contentSource, this.XmlReader.XmlBaseUri);
                }

                entry.MediaResource.ContentType = contentType;

                // Verify that the atom:content element is empty, since for MLEs there must be no content in-line.
                if (!this.XmlReader.TryReadEmptyElement())
                {
                    throw new ODataException(o.Strings.ODataAtomEntryAndFeedDeserializer_ContentWithSourceLinkIsNotEmpty);
                }
            }
            else
            {
                bool isContentTypeNullOrEmpty = string.IsNullOrEmpty(contentType);
                if (isContentTypeNullOrEmpty && this.AtomInputContext.UseClientFormatBehavior)
                {
                    this.XmlReader.SkipElementContent();
                }

                string mediaType = contentType;
                if (!isContentTypeNullOrEmpty)
                {
                    mediaType = this.VerifyAtomContentMediaType(contentType);
                }

                // Normal content with properties - not an MLE
                entryState.MediaLinkEntry = false;

                this.XmlReader.MoveToElement();
                if (!this.XmlReader.IsEmptyElement && this.XmlReader.NodeType != XmlNodeType.EndElement)
                {
                    if (string.IsNullOrEmpty(mediaType))
                    {
                        // Show "plain/text" media type behavior. Intentionally discard the value read.
                        this.XmlReader.ReadElementContentValue();
                    }
                    else
                    {
                        // The behavior to read atom:content is to ignore all non-element nodes and all elements
                        // that are not in the OData metadata namespace. If we find elements in the OData metadata
                        // namespace that we don't expect, we fail.
                        this.XmlReader.ReadStartElement();

                        while (this.XmlReader.NodeType != XmlNodeType.EndElement)
                        {
                            switch (this.XmlReader.NodeType)
                            {
                                case XmlNodeType.Element:
                                    // Test for an element in the OData metadata namespace
                                    if (this.XmlReader.NamespaceEquals(this.XmlReader.ODataMetadataNamespace))
                                    {
                                        // We fail on any elements in the OData metadata namespace except for the 'properties' element.
                                        if (!this.XmlReader.LocalNameEquals(this.AtomPropertiesElementName))
                                        {
                                            throw new ODataException(o.Strings.ODataAtomEntryAndFeedDeserializer_ContentWithInvalidNode(this.XmlReader.LocalName));
                                        }

                                        this.ValidateDuplicateElement(entryState.HasProperties && this.AtomInputContext.UseDefaultFormatBehavior);

                                        if (this.UseClientFormatBehavior && entryState.HasProperties)
                                        {
                                            this.XmlReader.SkipElementContent();
                                        }
                                        else
                                        {
                                            this.ReadProperties(entryState.EntityType, ReaderUtils.GetPropertiesList(entryState.Entry.Properties), entryState.DuplicatePropertyNamesChecker, /* epmPresent */ entryState.CachedEpm != null);
                                        }

                                        entryState.HasProperties = true;
                                    }
                                    else
                                    {
                                        // Ignore all elements in the non-OData metadata namespace
                                        this.XmlReader.SkipElementContent();
                                    }

                                    // Read over the m:properties end element (or empty start element)
                                    this.XmlReader.Read();

                                    break;
                                case XmlNodeType.EndElement:
                                    break;
                                default:
                                    // Skip over all non-element nodes
                                    this.XmlReader.Skip();
                                    break;
                            }
                        }
                    }
                }
            }

            // Read over the end element, or empty start element.
            this.XmlReader.Read();

            this.XmlReader.AssertNotBuffering();

            entryState.HasContent = true;
        }
Ejemplo n.º 12
0
 private EpmCustomReader(IODataAtomReaderEntryState entryState, ODataAtomInputContext inputContext) : base(entryState, inputContext)
 {
 }
Ejemplo n.º 13
0
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="entryState">The reader entry state for the entry to which the EPM is applied.</param>
 /// <param name="inputContext">The input context currently in use.</param>
 private EpmCustomReader(
     IODataAtomReaderEntryState entryState,
     ODataAtomInputContext inputContext)
     : base(entryState, inputContext)
 {
 }
        /// <summary>
        /// Reads an ATOM element inside the atom:entry from the input.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <returns>
        /// If the atom element is representing a navigation link a descriptor for that link is returned,
        /// otherwise null.
        /// </returns>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element in ATOM namespace - The element in ATOM namespace to read.
        /// Post-Condition: Any                                   - The node after the ATOM element if it's not a navigation link.
        ///                 XmlNodeType.Element atom:link         - The start tag of atom:link if it's a navigation link.
        /// </remarks>
        private ODataAtomReaderNavigationLinkDescriptor ReadAtomElementInEntry(IODataAtomReaderEntryState entryState)
        {
            Debug.Assert(entryState != null, "entryState != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace,
                "The reader must be on an element in the ATOM namespace for this method to work.");

            // ATOM elements
            if (this.XmlReader.LocalNameEquals(this.AtomContentElementName))
            {
                // atom:content
                this.ReadAtomContentElement(entryState);
            }
            else if (this.XmlReader.LocalNameEquals(this.AtomIdElementName))
            {
                // atom:id
                this.ReadAtomIdElementInEntry(entryState);
            }
            else if (this.XmlReader.LocalNameEquals(this.AtomCategoryElementName))
            {
                string attributeValue = this.XmlReader.GetAttribute(this.AtomCategorySchemeAttributeName, this.EmptyNamespace);

                if (attributeValue != null && string.CompareOrdinal(attributeValue, this.MessageReaderSettings.ReaderBehavior.ODataTypeScheme) == 0)
                {
                    this.ValidateDuplicateElement(entryState.HasTypeNameCategory && this.AtomInputContext.UseDefaultFormatBehavior);

                    if (this.ReadAtomMetadata)
                    {
                        entryState.AtomEntryMetadata.CategoryWithTypeName = this.EntryMetadataDeserializer.ReadAtomCategoryElement();
                    }
                    else
                    {
                        this.XmlReader.Skip();
                    }

                    entryState.HasTypeNameCategory = true;
                }
                else
                {
                    // atom:category
                    // If we don't have syndication EPM for category and we're not to store ATOM metadata, we can safely skip all category elements
                    // since we've already read the typename and no other category element holds anything of interest.
                    // That's true even if there are multiple category elements of interest, for typename we already took the first.
                    if (entryState.CachedEpm != null || this.ReadAtomMetadata)
                    {
                        this.EntryMetadataDeserializer.ReadAtomCategoryElementInEntryContent(entryState);
                    }
                    else
                    {
                        this.XmlReader.Skip();
                    }
                }
            }
            else if (this.XmlReader.LocalNameEquals(this.AtomLinkElementName))
            {
                // atom:link
                return this.ReadAtomLinkElementInEntry(entryState);
            }
            else
            {
                if (entryState.CachedEpm != null || this.ReadAtomMetadata)
                {
                    this.EntryMetadataDeserializer.ReadAtomElementInEntryContent(entryState);
                }
                else
                {
                    // Skip the element since we don't need it.
                    this.XmlReader.Skip();
                }
            }

            return null;
        }
        /// <summary>
        /// Reads the content of an entry (child nodes of the atom:entry, not the atom:content element).
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <returns>A descriptor representing the navigation link detected;
        /// null if no navigation link was found and the end of the entry was reached.</returns>
        /// <remarks>
        /// Pre-Condition:  Anything but Attribute - the child node of the atom:entry element, can be pretty much anything, the method will skip over insignificant nodes and text nodes if found.
        /// Post-Condition: XmlNodeType.EndElement atom:entry - The end of the atom:entry element if no nav. link was found and the end of the entry was reached.
        ///                 XmlNodeType.Element atom:link     - The start tag of the atom:link element representing a navigation link.
        /// </remarks>
        internal ODataAtomReaderNavigationLinkDescriptor ReadEntryContent(IODataAtomReaderEntryState entryState)
        {
            Debug.Assert(entryState != null, "entryState != null");
            this.XmlReader.AssertNotBuffering();
            Debug.Assert(this.XmlReader.NodeType != XmlNodeType.Attribute, "The reader must be positioned on a child node of the atom:entry element.");

            ODataAtomReaderNavigationLinkDescriptor navigationLinkDescriptor = null;

            while (this.XmlReader.NodeType != XmlNodeType.EndElement)
            {
                if (this.XmlReader.NodeType != XmlNodeType.Element)
                {
                    Debug.Assert(this.XmlReader.NodeType != XmlNodeType.EndElement, "EndElement should have been handled already.");

                    // Skip everything but elements, including insignificant nodes, text nodes and CDATA nodes
                    this.XmlReader.Skip();
                    continue;
                }

                if (this.XmlReader.NamespaceEquals(this.AtomNamespace))
                {
                    navigationLinkDescriptor = this.ReadAtomElementInEntry(entryState);
                    if (navigationLinkDescriptor != null)
                    {
                        entryState.DuplicatePropertyNamesChecker.CheckForDuplicatePropertyNamesOnNavigationLinkStart(navigationLinkDescriptor.NavigationLink);
                        break;
                    }
                }
                else if (this.XmlReader.NamespaceEquals(this.XmlReader.ODataMetadataNamespace))
                {
                    AtomInstanceAnnotation annotation;

                    if (this.XmlReader.LocalNameEquals(this.AtomPropertiesElementName))
                    {
                        // The default behavior is to disallow duplicates of entry/m:properties element defined in ODATA spec.
                        this.ValidateDuplicateElement(entryState.HasProperties && this.AtomInputContext.UseDefaultFormatBehavior);

                        // m:properties outside of content -> MLE
                        EnsureMediaResource(entryState);
                        this.ReadProperties(entryState.EntityType, entryState.Entry.Properties.ToReadOnlyEnumerable("Properties"), entryState.DuplicatePropertyNamesChecker);

                        // Read over the end element or the empty start element.
                        this.XmlReader.Read();

                        entryState.HasProperties = true;
                    }
                    else if (this.ReadingResponse && this.TryReadOperation(entryState))
                    {
                        // Reader versioning: Operations should only be read in response payloads if MPV >= V3 (but even in <V3 payloads).
                        // Nothing to do, the operation was already populated.
                    }
                    else if (this.atomAnnotationReader.TryReadAnnotation(out annotation))
                    {
                        // An annotation occurring as a direct child of an entry element can only target the entry it was found in.
                        // To target a property, the annotation must be found in the <m:properties> element.
                        // If we find an annotation breaking this rule, fail.
                        if (annotation.IsTargetingCurrentElement)
                        {
                            entryState.Entry.InstanceAnnotations.Add(new ODataInstanceAnnotation(annotation.TermName, annotation.Value));
                        }
                        else
                        {
                            throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_AnnotationWithNonDotTarget(annotation.Target, annotation.TermName));
                        }
                    }
                    else
                    {
                        // Ignore all other elements in the metadata namespace which we don't recognize (extensibility point)
                        this.XmlReader.Skip();
                    }
                }
                else
                {
                    this.XmlReader.Skip();
                }
            }

            Debug.Assert(
                this.XmlReader.NodeType != XmlNodeType.EndElement || 
                    (this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomEntryElementName),
                "EndElement found but for element other than atom:entry.");
            Debug.Assert(
                this.XmlReader.NodeType != XmlNodeType.Element ||
                    (this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName),
                "Only atom:link elements can be reported as navigation links.");

            this.AssertXmlCondition(XmlNodeType.Element, XmlNodeType.EndElement);
            this.XmlReader.AssertNotBuffering();
            return navigationLinkDescriptor;
        }
        /// <summary>
        /// Reads an ATOM element inside the atom:entry from the input.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <returns>
        /// If the atom element is representing a navigation link a descriptor for that link is returned,
        /// otherwise null.
        /// </returns>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element in ATOM namespace - The element in ATOM namespace to read.
        /// Post-Condition: Any                                   - The node after the ATOM element if it's not a navigation link.
        ///                 XmlNodeType.Element atom:link         - The start tag of atom:link if it's a navigation link.
        /// </remarks>
        private ODataAtomReaderNavigationLinkDescriptor ReadAtomElementInEntry(IODataAtomReaderEntryState entryState)
        {
            Debug.Assert(entryState != null, "entryState != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace,
                "The reader must be on an element in the ATOM namespace for this method to work.");

            // ATOM elements
            if (this.XmlReader.LocalNameEquals(this.AtomContentElementName))
            {
                // atom:content
                this.ReadAtomContentElement(entryState);
            }
            else if (this.XmlReader.LocalNameEquals(this.AtomIdElementName))
            {
                // atom:id
                this.ReadAtomIdElementInEntry(entryState);
            }
            else if (this.XmlReader.LocalNameEquals(this.AtomCategoryElementName))
            {
                string attributeValue = this.XmlReader.GetAttribute(this.AtomCategorySchemeAttributeName, this.EmptyNamespace);

                // Astoria client recognizes attributes in ATOM namespace as well when looking for category/@scheme.
                // However, when the WCF DS client behavior is turned-on, Atom metadata reading is turned off. So, we do not need 
                // to special case handling duplicate category elements for WCF DS client behavior (however, when finding type name, 
                // we will).
                if (attributeValue != null && string.CompareOrdinal(attributeValue, Atom.AtomConstants.ODataSchemeNamespace) == 0)
                {
                    // The default behavior is to disallow duplicates of entry/category element defined in ODATA spec.
                    // This category element was used to get the type. So, record that we have seen the category element with the 
                    // default scheme and skip the element. Do not read this element as atom metadata.
                    this.ValidateDuplicateElement(entryState.HasTypeNameCategory && this.AtomInputContext.UseDefaultFormatBehavior);

                    if (this.ReadAtomMetadata)
                    {
                        entryState.AtomEntryMetadata.CategoryWithTypeName = this.EntryMetadataDeserializer.ReadAtomCategoryElement();
                    }
                    else
                    {
                        this.XmlReader.Skip();
                    }

                    entryState.HasTypeNameCategory = true;
                }
                else
                {
                    // atom:category
                    // If we're not to store ATOM metadata, we can safely skip all category elements
                    // since we've already read the typename and no other category element holds anything of interest.
                    // That's true even if there are multiple category elements of interest, for typename we already took the first.
                    if (this.ReadAtomMetadata)
                    {
                        this.EntryMetadataDeserializer.ReadAtomCategoryElementInEntryContent(entryState);
                    }
                    else
                    {
                        this.XmlReader.Skip();
                    }
                }
            }
            else if (this.XmlReader.LocalNameEquals(this.AtomLinkElementName))
            {
                // atom:link
                return this.ReadAtomLinkElementInEntry(entryState);
            }
            else
            {
                if (this.ReadAtomMetadata)
                {
                    this.EntryMetadataDeserializer.ReadAtomElementInEntryContent(entryState);
                }
                else
                {
                    // Skip the element since we don't need it.
                    this.XmlReader.Skip();
                }
            }

            return null;
        }
        /// <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>
        /// Reads a an m:action or m:function in atom:entry.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <returns>true, if the m:action or m:function was read succesfully, false otherwise.</returns>
        /// <remarks>
        /// Pre-Condition:   XmlNodeType.Element m:action|m:function - The m:action or m:function element to read.
        /// Post-Condition:  Any                                     - The node after the m:action or m:function element if it was read by this method.
        ///                  XmlNodeType.Element m:action|m:function - The m:action or m:function element to read if it was not read by this method.
        /// </remarks>
        private bool TryReadOperation(IODataAtomReaderEntryState entryState)
        {
            Debug.Assert(entryState != null, "entryState != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.ODataMetadataNamespace,
                "The XML reader must be on a metadata (m:*) element for this method to work.");

            bool isAction = false;
            if (this.XmlReader.LocalNameEquals(this.ODataActionElementName))
            {
                // m:action
                isAction = true;
            }
            else if (!this.XmlReader.LocalNameEquals(this.ODataFunctionElementName))
            {
                // not an m:function either
                return false;
            }

            ODataOperation operation;
            if (isAction)
            {
                operation = new ODataAction();
                entryState.Entry.AddAction((ODataAction)operation);
            }
            else
            {
                operation = new ODataFunction();
                entryState.Entry.AddFunction((ODataFunction)operation);
            }

            string operationName = this.XmlReader.LocalName; // for error reporting
            while (this.XmlReader.MoveToNextAttribute())
            {
                if (this.XmlReader.NamespaceEquals(this.EmptyNamespace))
                {
                    string attributeValue = this.XmlReader.Value;
                    if (this.XmlReader.LocalNameEquals(this.ODataOperationMetadataAttribute))
                    {
                        // For metadata, if the URI is relative we don't attempt to make it absolute using the service
                        // base URI, because the ODataOperation metadata URI is relative to $metadata.
                        operation.Metadata = this.ProcessUriFromPayload(attributeValue, this.XmlReader.XmlBaseUri, /*makeAbsolute*/ false);
                    }
                    else if (this.XmlReader.LocalNameEquals(this.ODataOperationTargetAttribute))
                    {
                        operation.Target = this.ProcessUriFromPayload(attributeValue, this.XmlReader.XmlBaseUri);
                    }
                    else if (this.XmlReader.LocalNameEquals(this.ODataOperationTitleAttribute))
                    {
                        operation.Title = this.XmlReader.Value;
                    }

                    // skip unknown attributes
                }
            }

            if (operation.Metadata == null)
            {
                throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_OperationMissingMetadataAttribute(operationName));
            }

            if (operation.Target == null)
            {
                throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_OperationMissingTargetAttribute(operationName));
            }

            // skip the content of m:action/m:function
            this.XmlReader.Skip();
            return true;
        }
        /// <summary>
        /// Reads a navigation link in entry element.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <param name="linkRelation">The value of the rel attribute of the link to read, unescaped parsed URI.</param>
        /// <param name="linkHRef">The value of the href attribute of the link to read.</param>
        /// <returns>A descriptor of a navigation link if a navigation link was found; null otherwise.</returns>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element atom:link - the start tag of the atom:link element to read.
        /// Post-Condition: XmlNodeType.Element atom:link - the start tag of the atom:link element - the reader doesn't move
        /// </remarks>
        private ODataAtomReaderNavigationLinkDescriptor TryReadNavigationLinkInEntry(
            IODataAtomReaderEntryState entryState,
            string linkRelation,
            string linkHRef)
        {
            Debug.Assert(linkRelation != null, "linkRelation != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName,
                "The XML reader must be on the atom:link element for this method to work.");

            // We will ignore navigation links with empty property names
            string navigationLinkName = AtomUtils.GetNameFromAtomLinkRelationAttribute(linkRelation, AtomConstants.ODataNavigationPropertiesRelatedLinkRelationPrefix);
            if (string.IsNullOrEmpty(navigationLinkName))
            {
                return null;
            }

            // Lookup the property in metadata
            // Note that we already verified that the navigation link name is not empty.
            IEdmNavigationProperty navigationProperty = ReaderValidationUtils.ValidateNavigationPropertyDefined(navigationLinkName, entryState.EntityType, this.MessageReaderSettings);

            // Navigation link
            ODataNavigationLink navigationLink = new ODataNavigationLink { Name = navigationLinkName };

            // Get the type of the link
            string navigationLinkType = this.XmlReader.GetAttribute(this.AtomTypeAttributeName, this.EmptyNamespace);

            // [Astoria-ODataLib-Integration] Handling of type attribute value on atom:link element.
            // The behavior of ODataLib is:
            // Parse the type as content type
            //   Compare media type names ignoring case (as per spec), compare parameter type names ignoring case
            //   If it's application/atom+xml without type parameter or invalid type parameter - use it as a navigation link without specifying collection/singleton (pending metadata validation)
            //   If it's application/atom+xml with type='feed' - use it as a navigation link assuming it's a collection (pending metadata validation)
            //   If it's application/atom+xml with type='entry' - use it as a navigation link assuming it's a singleton (pending metadata validation)
            //   In any other case - skip this link and treat it as if it's not a navigation link.
            // Note that parsing the type means we may fail if it's not a valid content type.
            // Missing and invalid type attributes are allowed. We will infer the cardinality either from the model or when expanding the link.
            if (!string.IsNullOrEmpty(navigationLinkType))
            {
                // Fast path for most common link types
                bool hasEntryType, hasFeedType;
                bool isExactMatch = AtomUtils.IsExactNavigationLinkTypeMatch(navigationLinkType, out hasEntryType, out hasFeedType);
                if (!isExactMatch)
                {
                    // If the fast path did not work, we have to fully parse the media type.
                    string mediaTypeName, mediaTypeCharset;
                    IList<KeyValuePair<string, string>> contentTypeParameters = HttpUtils.ReadMimeType(navigationLinkType, out mediaTypeName, out mediaTypeCharset);
                    if (!HttpUtils.CompareMediaTypeNames(mediaTypeName, MimeConstants.MimeApplicationAtomXml))
                    {
                        return null;
                    }

                    string typeParameterValue = null;
                    if (contentTypeParameters != null)
                    {
                        for (int contentTypeParameterIndex = 0; contentTypeParameterIndex < contentTypeParameters.Count; contentTypeParameterIndex++)
                        {
                            KeyValuePair<string, string> contentTypeParameter = contentTypeParameters[contentTypeParameterIndex];
                            if (HttpUtils.CompareMediaTypeParameterNames(MimeConstants.MimeTypeParameterName, contentTypeParameter.Key))
                            {
                                typeParameterValue = contentTypeParameter.Value;
                                break;
                            }
                        }
                    }

                    if (typeParameterValue != null)
                    {
                        if (string.Compare(typeParameterValue, MimeConstants.MimeTypeParameterValueEntry, StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            hasEntryType = true;
                        }
                        else if (string.Compare(typeParameterValue, MimeConstants.MimeTypeParameterValueFeed, StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            hasFeedType = true;
                        }
                    }
                }

                if (hasEntryType)
                {
                    navigationLink.IsCollection = false;
                }
                else if (hasFeedType)
                {
                    navigationLink.IsCollection = true;
                }
            }

            // We allow missing HREF on a link and simply report null.
            if (linkHRef != null)
            {
                navigationLink.Url = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri);
            }

            this.XmlReader.MoveToElement();

            // Read and store ATOM link metadata (captures extra info like lang, title) if ATOM metadata reading is turned on.
            AtomLinkMetadata atomLinkMetadata = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef);
            if (atomLinkMetadata != null)
            {
                navigationLink.SetAnnotation(atomLinkMetadata);
            }

            return new ODataAtomReaderNavigationLinkDescriptor(navigationLink, navigationProperty);
        }
        /// <summary>
        /// Reads the atom:link element in atom:entry.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <returns>
        /// If the link is a navigation link the method returns a descriptor representing that link,
        /// otherwise the method returns null.
        /// </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 it's not a navigation link.
        ///                  XmlNodeType.Element atom:link  - The atom:link start tag if it's a navigation link.
        /// </remarks>
        private ODataAtomReaderNavigationLinkDescriptor ReadAtomLinkElementInEntry(IODataAtomReaderEntryState entryState)
        {
            Debug.Assert(entryState != null, "entryState != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName,
                "The XML reader must be on the atom:link element for this method to work.");

            // Read all the attributes which we will need for all the links (rel and href)
            // NOTE: this method does not move the reader; thus a potential xml:base definition on the element
            //       works correctly for the values read from the attributes until we move the reader.
            string linkRelation, linkHRef;
            this.ReadAtomLinkRelationAndHRef(out linkRelation, out linkHRef);

            // Standard relations like "edit" can be either "edit" or "IANANamespace/edit". The GetNameFromAtomLinkRelationAttribute
            // method is rather expensive (as it has to unescape the value, create a URI and then compare the prefix for the IANA namespace)
            // so we first compare the simple "edit" case, and only if that fails we try the "IANANamespace/edit" for all the standard
            // relations.
            if (linkRelation != null)
            {
                // [Astoria-ODataLib-Integration] Parsing of URLs on OData recognized places may fail, but Astoria server doesn't
                // [Astoria-ODataLib-Integration] Handling of invalid input data which WCF DS Server V2 ignores
                //   standard relation links are completely ignored by WCF DS V2, so we need to ignore them as well.
                bool isStreamPropertyLink = false;
                if (!this.AtomInputContext.UseServerFormatBehavior)
                {
                    if (this.TryReadAtomStandardRelationLinkInEntry(entryState, linkRelation, linkHRef))
                    {
                        return null;
                    }
                }

                // All the other checks require the rel attribute to be a valid URI value, so if the unescape operation returns null
                // we don't need to look at that link anymore.
                string unescapedLinkRelation = AtomUtils.UnescapeAtomLinkRelationAttribute(linkRelation);
                if (unescapedLinkRelation != null)
                {
                    if (!this.AtomInputContext.UseServerFormatBehavior)
                    {
                        string ianaRelation = AtomUtils.GetNameFromAtomLinkRelationAttribute(unescapedLinkRelation, AtomConstants.IanaLinkRelationsNamespace);
                        if (ianaRelation != null && this.TryReadAtomStandardRelationLinkInEntry(entryState, ianaRelation, linkHRef))
                        {
                            return null;
                        }
                    }

                    ODataAtomReaderNavigationLinkDescriptor navigationLinkDescriptor = this.TryReadNavigationLinkInEntry(entryState, unescapedLinkRelation, linkHRef);
                    if (navigationLinkDescriptor != null)
                    {
                        return navigationLinkDescriptor;
                    }

                    if (this.TryReadStreamPropertyLinkInEntry(entryState, unescapedLinkRelation, linkHRef, out isStreamPropertyLink))
                    {
                        return null;
                    }

                    if (!isStreamPropertyLink && this.TryReadAssociationLinkInEntry(entryState, unescapedLinkRelation, linkHRef))
                    {
                        return null;
                    }
                }
            }

            if (this.ReadAtomMetadata)
            {
                AtomLinkMetadata linkMetadata =
                    this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef);
                if (linkMetadata != null)
                {
                    entryState.AtomEntryMetadata.AddLink(linkMetadata);
                }
            }
            
            // Skip the element since we don't need it.
            this.XmlReader.Skip();
            
            return null;
        }
Ejemplo n.º 21
0
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="entryState">The reader entry state for the entry to which the EPM is applied.</param>
 /// <param name="inputContext">The input context currently in use.</param>
 private EpmSyndicationReader(
     IODataAtomReaderEntryState entryState,
     ODataAtomInputContext inputContext)
     : base(entryState, inputContext)
 {
 }
        /// <summary>
        /// Reads an element in ATOM namespace in the content of the entry element.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element (atom:*) - the ATOM element to read.
        /// Post-Condition: Any                          - the node after the ATOM element which was read.
        /// </remarks>
        internal void ReadAtomElementInEntryContent(IODataAtomReaderEntryState entryState)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(entryState != null, "entryState != null");
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace, "Only atom:* elements can be read by this method.");

            ODataEntityPropertyMappingCache cachedEpm            = entryState.CachedEpm;
            EpmTargetPathSegment            epmTargetPathSegment = null;

            if (cachedEpm != null)
            {
                epmTargetPathSegment = cachedEpm.EpmTargetTree.SyndicationRoot;
            }

            EpmTargetPathSegment subSegment;

            if (this.ShouldReadElement(epmTargetPathSegment, this.XmlReader.LocalName, out subSegment))
            {
                switch (this.XmlReader.LocalName)
                {
                case AtomConstants.AtomAuthorElementName:
                    this.ReadAuthorElement(entryState, subSegment);
                    return;

                case AtomConstants.AtomContributorElementName:
                    this.ReadContributorElement(entryState, subSegment);
                    return;

                case AtomConstants.AtomUpdatedElementName:
                {
                    AtomEntryMetadata entryMetadata = entryState.AtomEntryMetadata;
                    if (this.UseClientFormatBehavior)
                    {
                        if (this.ShouldReadSingletonElement(entryMetadata.UpdatedString != null))
                        {
                            entryMetadata.UpdatedString = this.ReadAtomDateConstructAsString();
                            return;
                        }
                    }
                    else
                    {
                        if (this.ShouldReadSingletonElement(entryMetadata.Updated.HasValue))
                        {
                            entryMetadata.Updated = this.ReadAtomDateConstruct();
                            return;
                        }
                    }
                }

                break;

                case AtomConstants.AtomPublishedElementName:
                {
                    AtomEntryMetadata entryMetadata = entryState.AtomEntryMetadata;
                    if (this.UseClientFormatBehavior)
                    {
                        if (this.ShouldReadSingletonElement(entryMetadata.PublishedString != null))
                        {
                            entryMetadata.PublishedString = this.ReadAtomDateConstructAsString();
                            return;
                        }
                    }
                    else
                    {
                        if (this.ShouldReadSingletonElement(entryMetadata.Published.HasValue))
                        {
                            entryMetadata.Published = this.ReadAtomDateConstruct();
                            return;
                        }
                    }
                }

                break;

                case AtomConstants.AtomRightsElementName:
                    if (this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Rights != null))
                    {
                        entryState.AtomEntryMetadata.Rights = this.ReadAtomTextConstruct();
                        return;
                    }

                    break;

                case AtomConstants.AtomSourceElementName:
                    if (this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Source != null))
                    {
                        entryState.AtomEntryMetadata.Source = this.ReadAtomSourceInEntryContent();
                        return;
                    }

                    break;

                case AtomConstants.AtomSummaryElementName:
                    if (this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Summary != null))
                    {
                        entryState.AtomEntryMetadata.Summary = this.ReadAtomTextConstruct();
                        return;
                    }

                    break;

                case AtomConstants.AtomTitleElementName:
                    if (this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Title != null))
                    {
                        entryState.AtomEntryMetadata.Title = this.ReadAtomTextConstruct();
                        return;
                    }

                    break;

                default:
                    break;
                }
            }

            // Skip everything we didn't read.
            this.XmlReader.Skip();
        }
Ejemplo n.º 23
0
        internal void ReadAtomElementInEntryContent(IODataAtomReaderEntryState entryState)
        {
            EpmTargetPathSegment            segment2;
            ODataEntityPropertyMappingCache cachedEpm     = entryState.CachedEpm;
            EpmTargetPathSegment            parentSegment = null;

            if (cachedEpm != null)
            {
                parentSegment = cachedEpm.EpmTargetTree.SyndicationRoot;
            }
            if (base.ShouldReadElement(parentSegment, base.XmlReader.LocalName, out segment2))
            {
                switch (base.XmlReader.LocalName)
                {
                case "author":
                    this.ReadAuthorElement(entryState, segment2);
                    return;

                case "contributor":
                    this.ReadContributorElement(entryState, segment2);
                    return;

                case "updated":
                {
                    AtomEntryMetadata atomEntryMetadata = entryState.AtomEntryMetadata;
                    if (!base.UseClientFormatBehavior)
                    {
                        if (!this.ShouldReadSingletonElement(atomEntryMetadata.Updated.HasValue))
                        {
                            break;
                        }
                        atomEntryMetadata.Updated = base.ReadAtomDateConstruct();
                        return;
                    }
                    if (!this.ShouldReadSingletonElement(atomEntryMetadata.UpdatedString != null))
                    {
                        break;
                    }
                    atomEntryMetadata.UpdatedString = base.ReadAtomDateConstructAsString();
                    return;
                }

                case "published":
                {
                    AtomEntryMetadata metadata2 = entryState.AtomEntryMetadata;
                    if (!base.UseClientFormatBehavior)
                    {
                        if (this.ShouldReadSingletonElement(metadata2.Published.HasValue))
                        {
                            metadata2.Published = base.ReadAtomDateConstruct();
                            return;
                        }
                        break;
                    }
                    if (!this.ShouldReadSingletonElement(metadata2.PublishedString != null))
                    {
                        break;
                    }
                    metadata2.PublishedString = base.ReadAtomDateConstructAsString();
                    return;
                }

                case "rights":
                    if (!this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Rights != null))
                    {
                        break;
                    }
                    entryState.AtomEntryMetadata.Rights = base.ReadAtomTextConstruct();
                    return;

                case "source":
                    if (!this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Source != null))
                    {
                        break;
                    }
                    entryState.AtomEntryMetadata.Source = this.ReadAtomSourceInEntryContent();
                    return;

                case "summary":
                    if (!this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Summary != null))
                    {
                        break;
                    }
                    entryState.AtomEntryMetadata.Summary = base.ReadAtomTextConstruct();
                    return;

                case "title":
                    if (!this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Title != null))
                    {
                        break;
                    }
                    entryState.AtomEntryMetadata.Title = base.ReadAtomTextConstruct();
                    return;
                }
            }
            base.XmlReader.Skip();
        }
        /// <summary>
        /// Reads the atom:link element with one of the standard relation values in the atom:entry element.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry 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>
        /// <returns>If the rel was one of the recognized standard relations and this method read the link
        /// the return value is true. Otherwise the method doesn't move the reader and returns 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 TryReadAtomStandardRelationLinkInEntry(IODataAtomReaderEntryState entryState, string linkRelation, string linkHRef)
        {
            Debug.Assert(entryState != null, "entryState != null");
            Debug.Assert(linkRelation != null, "linkRelation != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName,
                "The XML reader must be on the atom:link element for this method to work.");

            if (string.CompareOrdinal(linkRelation, AtomConstants.AtomEditRelationAttributeValue) == 0)
            {
                // Edit link
                //
                // First check whether we have already seen an edit link; if we have we throw in default format mode and
                // ignore any further edit links in WCF DS client or server mode.
                if (entryState.HasEditLink && !this.AtomInputContext.UseServerApiBehavior)
                {
                    throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_MultipleLinksInEntry(AtomConstants.AtomEditRelationAttributeValue));
                }

                // WCF DS server doesn't fail on atom:link which has no or empty href.
                if (linkHRef != null)
                {
                    entryState.Entry.EditLink = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri);
                }

                if (this.ReadAtomMetadata)
                {
                    entryState.AtomEntryMetadata.EditLink =
                        this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef);
                }

                entryState.HasEditLink = true;

                // Ignore content of atom:link elements (unless they are expanded nav. links)
                this.XmlReader.Skip();
                return true;
            }

            if (string.CompareOrdinal(linkRelation, AtomConstants.AtomSelfRelationAttributeValue) == 0)
            {
                // Self link
                //
                // First check whether we have already seen a self link; if we have we throw in default format mode and
                // ignore any further self links in WCF DS client or server mode.
                if (entryState.HasReadLink && !this.AtomInputContext.UseServerApiBehavior)
                {
                    throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_MultipleLinksInEntry(AtomConstants.AtomSelfRelationAttributeValue));
                }

                // WCF DS server doesn't fail on atom:link which has no or empty href.
                if (linkHRef != null)
                {
                    entryState.Entry.ReadLink = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri);
                }

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

                entryState.HasReadLink = true;

                // Ignore content of atom:link elements (unless they are expanded nav. links)
                this.XmlReader.Skip();
                return true;
            }

            if (string.CompareOrdinal(linkRelation, AtomConstants.AtomEditMediaRelationAttributeValue) == 0)
            {
                // edit-media link
                // The default ODataLib behavior should be to allow only one edit-media link in the OData payload.
                if (entryState.HasEditMediaLink && !this.AtomInputContext.UseServerApiBehavior)
                {
                    throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_MultipleLinksInEntry(AtomConstants.AtomEditMediaRelationAttributeValue));
                }

                
                // Note that we always mark the entry as MLE if we find the edit-media rel. This is so that we have a place to store the ATOM metadata for the link
                // (as we need the MediaResource property to be filled). We do so even without ATOM metadata to maintain consistent behavior.
                EnsureMediaResource(entryState);
                ODataEntry entry = entryState.Entry;

                // [Astoria-ODataLib-Integration] Astoria Server doesn't fail on atom:link which has no or empty href.
                if (linkHRef != null)
                {
                    entry.MediaResource.EditLink = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri);
                }

                string mediaETagValue = this.XmlReader.GetAttribute(this.ODataETagAttributeName, this.XmlReader.ODataMetadataNamespace);
                if (mediaETagValue != null)
                {
                    entry.MediaResource.ETag = mediaETagValue;
                }

                if (this.ReadAtomMetadata)
                {
                    AtomLinkMetadata linkMetadata = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef);

                    entry.MediaResource.SetAnnotation(
                        new AtomStreamReferenceMetadata
                        {
                            EditLink = linkMetadata
                        });
                }

                entryState.HasEditMediaLink = true;

                // Ignore content of atom:link elements (unless they are expanded nav. links)
                this.XmlReader.Skip();
                return true;
            }

            return false;
        }
        /// <summary>
        /// Reads a navigation link in entry element.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <param name="linkRelation">The value of the rel attribute of the link to read, unescaped parsed URI.</param>
        /// <param name="linkHRef">The value of the href attribute of the link to read.</param>
        /// <returns>A descriptor of a navigation link if a navigation link was found; null otherwise.</returns>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element atom:link - the start tag of the atom:link element to read.
        /// Post-Condition: XmlNodeType.Element atom:link - the start tag of the atom:link element - the reader doesn't move
        /// </remarks>
        private ODataAtomReaderNavigationLinkDescriptor TryReadNavigationLinkInEntry(
            IODataAtomReaderEntryState entryState,
            string linkRelation,
            string linkHRef)
        {
            Debug.Assert(linkRelation != null, "linkRelation != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName,
                "The XML reader must be on the atom:link element for this method to work.");

            string navigationLinkName = AtomUtils.GetNameFromAtomLinkRelationAttribute(linkRelation, AtomConstants.ODataNavigationPropertiesRelatedLinkRelationPrefix);
            if (string.IsNullOrEmpty(navigationLinkName))
            {
                return null;
            }

            // Lookup the property in metadata
            // Note that we already verified that the navigation link name is not empty.
            IEdmNavigationProperty navigationProperty = ReaderValidationUtils.ValidateNavigationPropertyDefined(navigationLinkName, entryState.EntityType, this.MessageReaderSettings);

            // Navigation link
            ODataNavigationLink navigationLink = new ODataNavigationLink { Name = navigationLinkName };

            // Get the type of the link
            string navigationLinkType = this.XmlReader.GetAttribute(this.AtomTypeAttributeName, this.EmptyNamespace);

            if (!string.IsNullOrEmpty(navigationLinkType))
            {
                // Fast path for most common link types
                bool hasEntryType, hasFeedType;
                bool isExactMatch = AtomUtils.IsExactNavigationLinkTypeMatch(navigationLinkType, out hasEntryType, out hasFeedType);
                if (!isExactMatch)
                {
                    // If the fast path did not work, we have to fully parse the media type.
                    string mediaTypeName, mediaTypeCharset;
                    IList<KeyValuePair<string, string>> contentTypeParameters = HttpUtils.ReadMimeType(navigationLinkType, out mediaTypeName, out mediaTypeCharset);
                    if (!HttpUtils.CompareMediaTypeNames(mediaTypeName, MimeConstants.MimeApplicationAtomXml))
                    {
                        return null;
                    }

                    string typeParameterValue = null;
                    if (contentTypeParameters != null)
                    {
                        for (int contentTypeParameterIndex = 0; contentTypeParameterIndex < contentTypeParameters.Count; contentTypeParameterIndex++)
                        {
                            KeyValuePair<string, string> contentTypeParameter = contentTypeParameters[contentTypeParameterIndex];
                            if (HttpUtils.CompareMediaTypeParameterNames(MimeConstants.MimeTypeParameterName, contentTypeParameter.Key))
                            {
                                typeParameterValue = contentTypeParameter.Value;
                                break;
                            }
                        }
                    }

                    if (typeParameterValue != null)
                    {
                        if (string.Compare(typeParameterValue, MimeConstants.MimeTypeParameterValueEntry, StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            hasEntryType = true;
                        }
                        else if (string.Compare(typeParameterValue, MimeConstants.MimeTypeParameterValueFeed, StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            hasFeedType = true;
                        }
                    }
                }

                if (hasEntryType)
                {
                    if (!this.UseClientFormatBehavior)
                    {
                        navigationLink.IsCollection = false;
                    }
                }
                else if (hasFeedType)
                {
                    navigationLink.IsCollection = true;
                }
            }

            if (linkHRef != null)
            {
                navigationLink.Url = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri);
            }

            this.XmlReader.MoveToElement();

            // Read and store ATOM link metadata (captures extra info like lang, title) if ATOM metadata reading is turned on.
            AtomLinkMetadata atomLinkMetadata = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef);
            if (atomLinkMetadata != null)
            {
                navigationLink.SetAnnotation(atomLinkMetadata);
            }

            return new ODataAtomReaderNavigationLinkDescriptor(navigationLink, navigationProperty);
        }
        /// <summary>
        /// Reads a stream property edit or read link in an atom:entry.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <param name="linkRelation">The rel attribute value for the link, unescaped parsed URI.</param>
        /// <param name="linkHRef">The href attribute value for the link (or null if the href attribute was not present).</param>
        /// <param name="isStreamPropertyLink">true if the link is a stream property read or edit link; otherwise false.</param>
        /// <returns>true, if the named stream was read successfully, false otherwise.</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 TryReadStreamPropertyLinkInEntry(IODataAtomReaderEntryState entryState, string linkRelation, string linkHRef, out bool isStreamPropertyLink)
        {
            Debug.Assert(entryState != null, "entryState != null");
            Debug.Assert(linkRelation != null, "linkRelation != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName,
                "The XML reader must be on the atom:link element for this method to work.");

            // check if this is an edit link
            string streamPropertyName = AtomUtils.GetNameFromAtomLinkRelationAttribute(linkRelation, AtomConstants.ODataStreamPropertyEditMediaRelatedLinkRelationPrefix);
            if (streamPropertyName != null)
            {
                isStreamPropertyLink = true;
                return this.ReadStreamPropertyLinkInEntry(entryState, streamPropertyName, linkRelation, linkHRef, /*editLink*/ true);
            }

            // check if this is a read link.
            streamPropertyName = AtomUtils.GetNameFromAtomLinkRelationAttribute(linkRelation, AtomConstants.ODataStreamPropertyMediaResourceRelatedLinkRelationPrefix);
            if (streamPropertyName != null)
            {
                isStreamPropertyLink = true;
                return this.ReadStreamPropertyLinkInEntry(entryState, streamPropertyName, linkRelation, linkHRef, /*editLink*/ false);
            }

            isStreamPropertyLink = false;
            return false;
        }
        /// <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 || this.Version < ODataVersion.V3)
            {
                return false;
            }

            if (streamPropertyName.Length == 0)
            {
                throw new ODataException(o.Strings.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
            Uri href = linkHRef == null ? null : this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri);

            if (editLink)
            {
                // edit-link
                if (streamReferenceValue.EditLink != null)
                {
                    throw new ODataException(o.Strings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithMultipleEditLinks(streamPropertyName));
                }

                streamReferenceValue.EditLink = href;

                if (this.ReadAtomMetadata)
                {
                    atomStreamMetadata.EditLink = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef);
                }
            }
            else
            {
                // read-link
                if (streamReferenceValue.ReadLink != null)
                {
                    throw new ODataException(o.Strings.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 (!HttpUtils.CompareMediaTypeNames(contentType, streamReferenceValue.ContentType))
                {
                    throw new ODataException(o.Strings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithMultipleContentTypes(streamPropertyName));
                }
            }

            streamReferenceValue.ContentType = contentType;

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

            this.XmlReader.Skip();
            return true;
        }
        /// <summary>
        /// Reads a an association link in atom:entry.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <param name="linkRelation">The rel attribute value for the link, unescaped parsed URI.</param>
        /// <param name="linkHRef">The href attribute value for the link (or null if the href attribute was not present).</param>
        /// <returns>true, if the association link was read succesfully, false otherwise.</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 TryReadAssociationLinkInEntry(IODataAtomReaderEntryState entryState, string linkRelation, string linkHRef)
        {
            Debug.Assert(entryState != null, "entryState != null");
            Debug.Assert(linkRelation != null, "linkRelation != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName,
                "The XML reader must be on the atom:link element for this method to work.");

            // We should not recognize association links with empty names
            // Do not recognize association links in requests as they cannot appear there as association links (but may be read as ATOM metadata links).
            // Reader versioning: do not recognize association links if MPV < V3, but read them even in <V3 payload if MPV >= V3.
            string associationLinkName = AtomUtils.GetNameFromAtomLinkRelationAttribute(linkRelation, AtomConstants.ODataNavigationPropertiesAssociationLinkRelationPrefix);
            if (string.IsNullOrEmpty(associationLinkName) || !this.ReadingResponse)
            {
                return false;
            }

            ReaderValidationUtils.ValidateNavigationPropertyDefined(associationLinkName, entryState.EntityType, this.MessageReaderSettings);

            // Get the type of the link
            string asssociationLinkType = this.XmlReader.GetAttribute(this.AtomTypeAttributeName, this.EmptyNamespace);

            // We fail if the type attribute is present, but the value is not 'application/xml'.
            if (asssociationLinkType != null &&
                !HttpUtils.CompareMediaTypeNames(asssociationLinkType, MimeConstants.MimeApplicationXml))
            {
                throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_InvalidTypeAttributeOnAssociationLink(associationLinkName));
            }

            Uri associationLinkUrl = null;

            // Allow null (we won't set the Url property) and empty (relative URL) values.
            // If the href is missing we will simply report null Url for the association link.
            if (linkHRef != null)
            {
                associationLinkUrl = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri);
            }

            ReaderUtils.CheckForDuplicateAssociationLinkAndUpdateNavigationLink(entryState.DuplicatePropertyNamesChecker, associationLinkName, associationLinkUrl);

            // TODO: Association Link - Add back support for customizing association link element in Atom

            // Ignore content of atom:link elements (unless they are expanded nav. links)
            this.XmlReader.Skip();
            return true;
        }
        /// <summary>
        /// Reads a an association link in atom:entry.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <param name="linkRelation">The rel attribute value for the link, unescaped parsed URI.</param>
        /// <param name="linkHRef">The href attribute value for the link (or null if the href attribute was not present).</param>
        /// <returns>true, if the association link was read succesfully, false otherwise.</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 TryReadAssociationLinkInEntry(IODataAtomReaderEntryState entryState, string linkRelation, string linkHRef)
        {
            Debug.Assert(entryState != null, "entryState != null");
            Debug.Assert(linkRelation != null, "linkRelation != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName,
                "The XML reader must be on the atom:link element for this method to work.");

            string associationLinkName = AtomUtils.GetNameFromAtomLinkRelationAttribute(linkRelation, AtomConstants.ODataNavigationPropertiesAssociationLinkRelationPrefix);
            if (string.IsNullOrEmpty(associationLinkName) || !this.ReadingResponse || this.MessageReaderSettings.MaxProtocolVersion < ODataVersion.V3)
            {
                return false;
            }

            ReaderValidationUtils.ValidateNavigationPropertyDefined(associationLinkName, entryState.EntityType, this.MessageReaderSettings);

            // Get the type of the link
            string asssociationLinkType = this.XmlReader.GetAttribute(this.AtomTypeAttributeName, this.EmptyNamespace);

            if (asssociationLinkType != null &&
                !HttpUtils.CompareMediaTypeNames(asssociationLinkType, MimeConstants.MimeApplicationXml))
            {
                throw new ODataException(o.Strings.ODataAtomEntryAndFeedDeserializer_InvalidTypeAttributeOnAssociationLink(associationLinkName));
            }

            ODataAssociationLink associationLink = new ODataAssociationLink { Name = associationLinkName };

            // Allow null (we won't set the Url property) and empty (relative URL) values.
            if (linkHRef != null)
            {
                associationLink.Url = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri);
            }

            entryState.DuplicatePropertyNamesChecker.CheckForDuplicatePropertyNames(associationLink);
            ReaderUtils.AddAssociationLinkToEntry(entryState.Entry, associationLink);

            // Read and store ATOM link metadata (captures extra info like lang, title) if ATOM metadata reading is turned on.
            AtomLinkMetadata atomLinkMetadata = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef);
            if (atomLinkMetadata != null)
            {
                associationLink.SetAnnotation(atomLinkMetadata);
            }

            this.XmlReader.Skip();
            return true;
        }
        /// <summary>
        /// Ensure a media resource is created for the specified entry.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        internal static void EnsureMediaResource(IODataAtomReaderEntryState entryState)
        {
            Debug.Assert(entryState != null, "entryState != null");

            entryState.MediaLinkEntry = true;

            ODataEntry entry = entryState.Entry;
            if (entry.MediaResource == null)
            {
                entry.MediaResource = new ODataStreamReferenceValue();
            }
        }
        /// <summary>
        /// Ensure a media resource is created for the specified entry.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <param name="validateMLEPresence">If set to true, this method will validate that marking the entry as MLE
        /// doesn't collide with it already being marked as non-MLE.</param>
        internal static void EnsureMediaResource(IODataAtomReaderEntryState entryState, bool validateMLEPresence)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(entryState != null, "entryState != null");

            if (validateMLEPresence)
            {
                entryState.MediaLinkEntry = true;
            }

            ODataEntry entry = entryState.Entry;
            if (entry.MediaResource == null)
            {
                entry.MediaResource = new ODataStreamReferenceValue();
            }
        }
        /// <summary>
        /// Returns an existing stream property value if it already exists in the list of OData properties otherwise creates a new 
        /// ODataProperty for the stream property and returns the value of that property.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <param name="streamPropertyName">The name of the stream property to return.</param>
        /// <returns>A new or an existing stream property value.</returns>
        private ODataStreamReferenceValue GetNewOrExistingStreamPropertyValue(IODataAtomReaderEntryState entryState, string streamPropertyName)
        {
            Debug.Assert(entryState != null, "entryState != null");
            Debug.Assert(streamPropertyName != null, "streamPropertyName != null");

            ReadOnlyEnumerable<ODataProperty> properties = entryState.Entry.Properties.ToReadOnlyEnumerable("Properties");

            // Property names are case sensitive, so compare in a case sensitive way.
            ODataProperty streamProperty = properties.FirstOrDefault(p => String.CompareOrdinal(p.Name, streamPropertyName) == 0);

            ODataStreamReferenceValue streamReferenceValue;
            if (streamProperty == null)
            {
                // The ValidateLinkPropertyDefined will fail if a stream property is not defined and the reader settings don't allow
                // reporting undeclared link properties. So if the method returns null, it means report the undeclared property anyway.
                IEdmProperty streamEdmProperty = ReaderValidationUtils.ValidateLinkPropertyDefined(streamPropertyName, entryState.EntityType, this.MessageReaderSettings);

                streamReferenceValue = new ODataStreamReferenceValue();
                streamProperty = new ODataProperty
                {
                    Name = streamPropertyName,
                    Value = streamReferenceValue
                };

                ReaderValidationUtils.ValidateStreamReferenceProperty(streamProperty, entryState.EntityType, streamEdmProperty, this.MessageReaderSettings);
                entryState.DuplicatePropertyNamesChecker.CheckForDuplicatePropertyNames(streamProperty);
                properties.AddToSourceList(streamProperty);
            }
            else
            {
                streamReferenceValue = streamProperty.Value as ODataStreamReferenceValue;
                if (streamReferenceValue == null)
                {
                    throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_StreamPropertyDuplicatePropertyName(streamPropertyName));
                }
            }

            return streamReferenceValue;
        }
        /// <summary>
        /// Reads the content of an entry (child nodes of the atom:entry, not the atom:content element).
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <returns>A descriptor representing the navigation link detected;
        /// null if no navigation link was found and the end of the entry was reached.</returns>
        /// <remarks>
        /// Pre-Condition:  Anything but Attribute - the child node of the atom:entry element, can be pretty much anything, the method will skip over insignificant nodes and text nodes if found.
        /// Post-Condition: XmlNodeType.EndElement atom:entry - The end of the atom:entry element if no nav. link was found and the end of the entry was reached.
        ///                 XmlNodeType.Element atom:link     - The start tag of the atom:link element representing a navigation link.
        /// </remarks>
        internal ODataAtomReaderNavigationLinkDescriptor ReadEntryContent(IODataAtomReaderEntryState entryState)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(entryState != null, "entryState != null");
            this.XmlReader.AssertNotBuffering();
            Debug.Assert(this.XmlReader.NodeType != XmlNodeType.Attribute, "The reader must be positioned on a child node of the atom:entry element.");

            ODataAtomReaderNavigationLinkDescriptor navigationLinkDescriptor = null;

            while (this.XmlReader.NodeType != XmlNodeType.EndElement)
            {
                if (this.XmlReader.NodeType != XmlNodeType.Element)
                {
                    Debug.Assert(this.XmlReader.NodeType != XmlNodeType.EndElement, "EndElement should have been handled already.");

                    // Skip everything but elements, including insignificant nodes, text nodes and CDATA nodes
                    this.XmlReader.Skip();
                    continue;
                }

                if (this.XmlReader.NamespaceEquals(this.AtomNamespace))
                {
                    navigationLinkDescriptor = this.ReadAtomElementInEntry(entryState);
                    if (navigationLinkDescriptor != null)
                    {
                        entryState.DuplicatePropertyNamesChecker.CheckForDuplicatePropertyNamesOnNavigationLinkStart(navigationLinkDescriptor.NavigationLink);
                        break;
                    }
                }
                else if (this.XmlReader.NamespaceEquals(this.XmlReader.ODataMetadataNamespace))
                {
                    if (this.XmlReader.LocalNameEquals(this.AtomPropertiesElementName))
                    {
                        this.ValidateDuplicateElement(entryState.HasProperties && this.AtomInputContext.UseDefaultFormatBehavior);

                        // m:properties outside of content -> MLE
                        EnsureMediaResource(entryState, /*validateMLEPresense*/ true);
                        this.ReadProperties(entryState.EntityType, ReaderUtils.GetPropertiesList(entryState.Entry.Properties), entryState.DuplicatePropertyNamesChecker, /* epmPresent */ entryState.CachedEpm != null);

                        // Read over the end element or the empty start element.
                        this.XmlReader.Read();

                        entryState.HasProperties = true;
                    }
                    else if (this.MessageReaderSettings.MaxProtocolVersion >= ODataVersion.V3 && this.ReadingResponse && this.TryReadOperation(entryState))
                    {
                    }
                    else if (this.EntryMetadataDeserializer.TryReadExtensionElementInEntryContent(entryState))
                    {
                        // Nothing to do, the extension element was already consumed.
                    }
                    else
                    {
                        // Ignore all other elements in the metadata namespace which we don't recognize (extensibility point)
                        this.XmlReader.Skip();
                    }
                }
                else
                {
                    // non-ATOM elements
                    // Read it for EPM and skip the rest.
                    if (!this.EntryMetadataDeserializer.TryReadExtensionElementInEntryContent(entryState))
                    {
                        this.XmlReader.Skip();
                    }
                }
            }

            Debug.Assert(
                this.XmlReader.NodeType != XmlNodeType.EndElement || 
                    (this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomEntryElementName),
                "EndElement found but for element other than atom:entry.");
            Debug.Assert(
                this.XmlReader.NodeType != XmlNodeType.Element ||
                    (this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName),
                "Only atom:link elements can be reported as navigation links.");

            this.AssertXmlCondition(XmlNodeType.Element, XmlNodeType.EndElement);
            this.XmlReader.AssertNotBuffering();
            return navigationLinkDescriptor;
        }
        /// <summary>
        /// Reads the atom:content element.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <remarks>
        /// Pre-Condition:   XmlNodeType.Element atom:content  - The atom:content element to read.
        /// Post-Condition:  Any                               - The node after the atom:content element.
        /// </remarks>
        private void ReadAtomContentElement(IODataAtomReaderEntryState entryState)
        {
            Debug.Assert(entryState != null, "entryState != null");
            this.XmlReader.AssertNotBuffering();
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomContentElementName,
                "The XML reader must be on the atom:content element for this method to work.");

            // The default behavior is to disallow duplicates of entry/content element defined in ODATA spec.
            this.ValidateDuplicateElement(entryState.HasContent && this.AtomInputContext.UseDefaultFormatBehavior);

            // atom:content
            // Read the attributes - we're interested in type and src
            string contentType;
            string contentSource;
            this.ReadAtomContentAttributes(out contentType, out contentSource);

            if (contentSource != null)
            {
                // atom:content/@src means this is an MLE
                ODataEntry entry = entryState.Entry;
                EnsureMediaResource(entryState);

                // Parsing of URLs on OData recognized places may fail, but Astoria server doesn't
                if (!this.AtomInputContext.UseServerFormatBehavior)
                {
                    entry.MediaResource.ReadLink = this.ProcessUriFromPayload(contentSource, this.XmlReader.XmlBaseUri);
                }

                entry.MediaResource.ContentType = contentType;

                // Verify that the atom:content element is empty, since for MLEs there must be no content in-line.
                if (!this.XmlReader.TryReadEmptyElement())
                {
                    throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_ContentWithSourceLinkIsNotEmpty);
                }
            }
            else
            {
                string mediaType = contentType;
                if (!string.IsNullOrEmpty(contentType))
                {
                    mediaType = VerifyAtomContentMediaType(contentType);
                }

                // Normal content with properties - not an MLE
                entryState.MediaLinkEntry = false;

                this.XmlReader.MoveToElement();
                if (!this.XmlReader.IsEmptyElement && this.XmlReader.NodeType != XmlNodeType.EndElement)
                {
                    if (string.IsNullOrEmpty(mediaType))
                    {
                        // Show "plain/text" media type behavior. Intentionally discard the value read.
                        this.XmlReader.ReadElementContentValue();
                    }
                    else
                    {
                        // The behavior to read atom:content is to ignore all non-element nodes and all elements
                        // that are not in the OData metadata namespace. If we find elements in the OData metadata
                        // namespace that we don't expect, we fail.
                        //     Client behavior around m:properties in atom:content
                        //
                        //     Server accepts payloads with anything but m:properties in atom:content by ignoring it.
                        //     We decided to adopt the same behavior of ignoring nodes and elements not in the OData 
                        //     metadata namespace for the client/server behavior.
                        //
                        // Move to the first child node of the atom:content
                        this.XmlReader.ReadStartElement();

                        while (this.XmlReader.NodeType != XmlNodeType.EndElement)
                        {
                            switch (this.XmlReader.NodeType)
                            {
                                case XmlNodeType.Element:
                                    // Test for an element in the OData metadata namespace
                                    if (this.XmlReader.NamespaceEquals(this.XmlReader.ODataMetadataNamespace))
                                    {
                                        // We fail on any elements in the OData metadata namespace except for the 'properties' element.
                                        if (!this.XmlReader.LocalNameEquals(this.AtomPropertiesElementName))
                                        {
                                            throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_ContentWithInvalidNode(this.XmlReader.LocalName));
                                        }

                                        // The default behavior is to disallow duplicates of entry/m:properties element defined in ODATA spec.
                                        // The default behavior is to disallow duplicates of entry/content/m:properties element defined in ODATA spec.
                                        this.ValidateDuplicateElement(entryState.HasProperties);
                                        this.ReadProperties(entryState.EntityType, entryState.Entry.Properties.ToReadOnlyEnumerable("Properties"), entryState.DuplicatePropertyNamesChecker);

                                        entryState.HasProperties = true;
                                    }
                                    else
                                    {
                                        // Ignore all elements in the non-OData metadata namespace
                                        this.XmlReader.SkipElementContent();
                                    }

                                    // Read over the m:properties end element (or empty start element)
                                    this.XmlReader.Read();

                                    break;
                                case XmlNodeType.EndElement:
                                    break;
                                default:
                                    // Skip over all non-element nodes
                                    this.XmlReader.Skip();
                                    break;
                            }
                        }
                    }
                }
            }

            // Read over the end element, or empty start element.
            this.XmlReader.Read();

            this.XmlReader.AssertNotBuffering();

            entryState.HasContent = true;
        }
Ejemplo n.º 35
0
 internal static void ReadEntryEpm(IODataAtomReaderEntryState entryState, ODataAtomInputContext inputContext)
 {
     new EpmCustomReader(entryState, inputContext).ReadEntryEpm();
 }