Beispiel #1
0
        /// <summary>
        /// Writes the custom mapped EPM properties to an XML writer which is expected to be positioned such to write
        /// a child element of the entry element.
        /// </summary>
        /// <param name="writer">The XmlWriter to write to.</param>
        /// <param name="epmTargetTree">The EPM target tree to use.</param>
        /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param>
        /// <param name="resourceType">The resource type of the entry.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        internal static void WriteEntryEpm(
            XmlWriter writer,
            EpmTargetTree epmTargetTree,
            EntryPropertiesValueCache epmValueCache,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(writer != null, "writer != null");
            Debug.Assert(epmTargetTree != null, "epmTargetTree != null");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");
            Debug.Assert(resourceType != null, "For any EPM to exist the metadata must be available.");

            // If there are no custom mappings, just return null.
            EpmTargetPathSegment customRootSegment = epmTargetTree.NonSyndicationRoot;

            Debug.Assert(customRootSegment != null, "EPM Target tree must always have non-syndication root.");
            if (customRootSegment.SubSegments.Count == 0)
            {
                return;
            }

            foreach (EpmTargetPathSegment targetSegment in customRootSegment.SubSegments)
            {
                Debug.Assert(!targetSegment.IsAttribute, "Target segments under the custom root must be for elements only.");

                string alreadyDeclaredPrefix = null;
                WriteElementEpm(writer, targetSegment, epmValueCache, resourceType, metadata, ref alreadyDeclaredPrefix);
            }
        }
Beispiel #2
0
        /// <summary>
        /// Writes an EPM attribute target.
        /// </summary>
        /// <param name="writer">The writer to write to.</param>
        /// <param name="targetSegment">The target segment describing the attribute to write.</param>
        /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param>
        /// <param name="resourceType">The resource type of the entry.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <param name="alreadyDeclaredPrefix">The name of the prefix if it was already declared.</param>
        private static void WriteAttributeEpm(
            XmlWriter writer,
            EpmTargetPathSegment targetSegment,
            EntryPropertiesValueCache epmValueCache,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata,
            ref string alreadyDeclaredPrefix)
        {
            Debug.Assert(writer != null, "writer != null");
            Debug.Assert(targetSegment != null && targetSegment.IsAttribute, "Only attribute target segments are supported by this method.");
            Debug.Assert(targetSegment.HasContent, "Attribute target segments must have content.");

            string textPropertyValue = GetEntryPropertyValueAsText(targetSegment, epmValueCache, resourceType, metadata);

            Debug.Assert(textPropertyValue != null, "Text value of a property mapped to attribute must not be null, the GetEntryPropertyValueAsText should take care of that.");

            // If the prefix is null, the WCF DS will still write it as the default namespace, so we need it to be an empty string.
            string attributePrefix = targetSegment.SegmentNamespacePrefix ?? string.Empty;

            writer.WriteAttributeString(attributePrefix, targetSegment.AttributeName, targetSegment.SegmentNamespaceUri, textPropertyValue);

            // Write out the declaration explicitely only if the prefix is not empty (just like the WCF DS does)
            if (attributePrefix.Length > 0)
            {
                WriteNamespaceDeclaration(writer, targetSegment, ref alreadyDeclaredPrefix);
            }
        }
Beispiel #3
0
        /// <summary>
        /// Given a target segment the method returns the text value of the property mapped to that segment to be used in EPM.
        /// </summary>
        /// <param name="targetSegment">The target segment to read the value for.</param>
        /// <param name="epmValueCache">The entry EPM value cache to use.</param>
        /// <param name="resourceType">The resource type of the entry being processed.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <returns>The test representation of the value, or the method throws if the text representation was not possible to obtain.</returns>
        private static string GetEntryPropertyValueAsText(
            EpmTargetPathSegment targetSegment,
            EntryPropertiesValueCache epmValueCache,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata)
        {
            Debug.Assert(targetSegment != null, "targetSegment != null");
            Debug.Assert(targetSegment.HasContent, "The target segment to read property for must have content.");
            Debug.Assert(targetSegment.EpmInfo != null, "The EPM info must be available on the target segment to read its property.");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");
            Debug.Assert(resourceType != null, "resourceType != null");

            bool   nullOnParentProperty;
            object propertyValue = targetSegment.EpmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty);

            if (propertyValue == null)
            {
                // TODO: In V3 when we use new format for null values using the m:null attribute we need to check here
                // if we're mapping into an attribute, in which case we should fail (can't write null to attribute in V3)
                // or if we're writing into element return true null, to use the m:null attribute.
                // In V2 nulls are written out as empty string always (and they're written into content as well)
                return(string.Empty);
            }
            else
            {
                return(EpmWriterUtils.GetPropertyValueAsText(propertyValue));
            }
        }
        /// <summary>
        /// Writes the custom mapped EPM properties to an XML writer which is expected to be positioned such to write
        /// a child element of the entry element.
        /// </summary>
        /// <param name="writer">The XmlWriter to write to.</param>
        /// <param name="epmTargetTree">The EPM target tree to use.</param>
        /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param>
        /// <param name="resourceType">The resource type of the entry.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        internal static void WriteEntryEpm(
            XmlWriter writer,
            EpmTargetTree epmTargetTree,
            EntryPropertiesValueCache epmValueCache,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(writer != null, "writer != null");
            Debug.Assert(epmTargetTree != null, "epmTargetTree != null");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");
            Debug.Assert(resourceType != null, "For any EPM to exist the metadata must be available.");

            // If there are no custom mappings, just return null.
            EpmTargetPathSegment customRootSegment = epmTargetTree.NonSyndicationRoot;
            Debug.Assert(customRootSegment != null, "EPM Target tree must always have non-syndication root.");
            if (customRootSegment.SubSegments.Count == 0)
            {
                return;
            }

            foreach (EpmTargetPathSegment targetSegment in customRootSegment.SubSegments)
            {
                Debug.Assert(!targetSegment.IsAttribute, "Target segments under the custom root must be for elements only.");

                string alreadyDeclaredPrefix = null;
                WriteElementEpm(writer, targetSegment, epmValueCache, resourceType, metadata, ref alreadyDeclaredPrefix);
            }
        }
Beispiel #5
0
        /// <summary>
        /// Writes an EPM element target.
        /// </summary>
        /// <param name="writer">The writer to write to.</param>
        /// <param name="targetSegment">The target segment describing the element to write.</param>
        /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param>
        /// <param name="resourceType">The resource type of the entry.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <param name="alreadyDeclaredPrefix">The name of the prefix if it was already declared.</param>
        private static void WriteElementEpm(
            XmlWriter writer,
            EpmTargetPathSegment targetSegment,
            EntryPropertiesValueCache epmValueCache,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata,
            ref string alreadyDeclaredPrefix)
        {
            Debug.Assert(writer != null, "writer != null");
            Debug.Assert(targetSegment != null && !targetSegment.IsAttribute, "Only element target segments are supported by this method.");

            // If the prefix is null, the WCF DS will still write it as the default namespace, so we need it to be an empty string.
            string elementPrefix = targetSegment.SegmentNamespacePrefix ?? string.Empty;

            writer.WriteStartElement(elementPrefix, targetSegment.SegmentName, targetSegment.SegmentNamespaceUri);

            // Write out the declaration explicitly only if the prefix is not empty (just like the WCF DS does)
            if (elementPrefix.Length > 0)
            {
                WriteNamespaceDeclaration(writer, targetSegment, ref alreadyDeclaredPrefix);
            }

            // Serialize the sub segment attributes first
            foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments)
            {
                if (subSegment.IsAttribute)
                {
                    WriteAttributeEpm(writer, subSegment, epmValueCache, resourceType, metadata, ref alreadyDeclaredPrefix);
                }
            }

            if (targetSegment.HasContent)
            {
                Debug.Assert(!targetSegment.SubSegments.Any(subSegment => !subSegment.IsAttribute), "If the segment has a content, it must not have any element children.");

                string textPropertyValue = GetEntryPropertyValueAsText(targetSegment, epmValueCache, resourceType, metadata);

                // TODO: In V3 we should check for textPropertyValue == null and write out the m:null in that case.
                Debug.Assert(textPropertyValue != null, "Null property value should not get here, the GetEntryPropertyValueAsText should take care of that for now.");

                writer.WriteString(textPropertyValue);
            }
            else
            {
                // Serialize the sub segment elements now
                foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments)
                {
                    if (!subSegment.IsAttribute)
                    {
                        WriteElementEpm(writer, subSegment, epmValueCache, resourceType, metadata, ref alreadyDeclaredPrefix);
                    }
                }
            }

            // Close the element
            writer.WriteEndElement();
        }
        /// <summary>
        /// Writes an EPM element target.
        /// </summary>
        /// <param name="writer">The writer to write to.</param>
        /// <param name="targetSegment">The target segment describing the element to write.</param>
        /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param>
        /// <param name="resourceType">The resource type of the entry.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <param name="alreadyDeclaredPrefix">The name of the prefix if it was already declared.</param>
        private static void WriteElementEpm(
            XmlWriter writer, 
            EpmTargetPathSegment targetSegment, 
            EntryPropertiesValueCache epmValueCache, 
            ResourceType resourceType, 
            DataServiceMetadataProviderWrapper metadata,
            ref string alreadyDeclaredPrefix)
        {
            Debug.Assert(writer != null, "writer != null");
            Debug.Assert(targetSegment != null && !targetSegment.IsAttribute, "Only element target segments are supported by this method.");

            // If the prefix is null, the WCF DS will still write it as the default namespace, so we need it to be an empty string.
            string elementPrefix = targetSegment.SegmentNamespacePrefix ?? string.Empty;
            writer.WriteStartElement(elementPrefix, targetSegment.SegmentName, targetSegment.SegmentNamespaceUri);

            // Write out the declaration explicitly only if the prefix is not empty (just like the WCF DS does)
            if (elementPrefix.Length > 0)
            {
                WriteNamespaceDeclaration(writer, targetSegment, ref alreadyDeclaredPrefix);
            }

            // Serialize the sub segment attributes first
            foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments)
            {
                if (subSegment.IsAttribute)
                {
                    WriteAttributeEpm(writer, subSegment, epmValueCache, resourceType, metadata, ref alreadyDeclaredPrefix);
                }
            }

            if (targetSegment.HasContent)
            {
                Debug.Assert(!targetSegment.SubSegments.Any(subSegment => !subSegment.IsAttribute), "If the segment has a content, it must not have any element children.");

                string textPropertyValue = GetEntryPropertyValueAsText(targetSegment, epmValueCache, resourceType, metadata);

                // TODO: In V3 we should check for textPropertyValue == null and write out the m:null in that case.
                Debug.Assert(textPropertyValue != null, "Null property value should not get here, the GetEntryPropertyValueAsText should take care of that for now.");

                writer.WriteString(textPropertyValue);
            }
            else
            {
                // Serialize the sub segment elements now
                foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments)
                {
                    if (!subSegment.IsAttribute)
                    {
                        WriteElementEpm(writer, subSegment, epmValueCache, resourceType, metadata, ref alreadyDeclaredPrefix);
                    }
                }
            }

            // Close the element
            writer.WriteEndElement();
        }
        /// <summary>
        /// Given a target segment the method returns the text value of the property mapped to that segment to be used in EPM.
        /// </summary>
        /// <param name="targetSegment">The target segment to read the value for.</param>
        /// <param name="epmValueCache">The entry EPM value cache to use.</param>
        /// <param name="resourceType">The resource type of the entry being processed.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <returns>The test representation of the value, or the method throws if the text representation was not possible to obtain.</returns>
        private static string GetEntryPropertyValueAsText(
            EpmTargetPathSegment targetSegment,
            EntryPropertiesValueCache epmValueCache,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata)
        {
            Debug.Assert(targetSegment != null, "targetSegment != null");
            Debug.Assert(targetSegment.HasContent, "The target segment to read property for must have content.");
            Debug.Assert(targetSegment.EpmInfo != null, "The EPM info must be available on the target segment to read its property.");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");
            Debug.Assert(resourceType != null, "resourceType != null");

            bool   nullOnParentProperty;
            object propertyValue = targetSegment.EpmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty);

            return(EpmWriterUtils.GetPropertyValueAsText(propertyValue));
        }
        /// <summary>
        /// Reads a property value starting on an entry.
        /// </summary>
        /// <param name="epmValueCache">The EPM value cache for the entry to read from.</param>
        /// <param name="resourceType">The resource type of the entry.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <param name="nullOnParentProperty">true if the value of the property is null because one of its parent properties was null, in this case
        /// the return value of the method is always null. false if the value of the property is the actual property value which may or may not be null.</param>
        /// <returns>The value of the property (may be null), or null if the property itself was not found due to one of its parent properties being null.</returns>
        internal object ReadEntryPropertyValue(
            EntryPropertiesValueCache epmValueCache,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata,
            out bool nullOnParentProperty)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(this.propertyValuePath != null, "The propertyValuePath should have been initialized by now.");
            Debug.Assert(this.propertyValuePath.Length > 0, "The propertyValuePath must not be empty for an entry property.");
            Debug.Assert(resourceType != null, "resourceType != null");

            // TODO - verify that we actually need the internal property PropertyValuePath
            // TODO - It might be possible to avoid the "value" type checks below if we do property value validation based on the resource type
            return(this.ReadPropertyValue(
                       epmValueCache.EntryProperties,
                       0,
                       resourceType,
                       metadata,
                       epmValueCache,
                       out nullOnParentProperty));
        }
		/// <summary>
		/// Reads a property value starting on an entry.
		/// </summary>
		/// <param name="epmValueCache">The EPM value cache for the entry to read from.</param>
		/// <param name="resourceType">The resource type of the entry.</param>
		/// <param name="metadata">The metadata provider to use.</param>
		/// <param name="nullOnParentProperty">true if the value of the property is null because one of its parent properties was null, in this case
		/// the return value of the method is always null. false if the value of the property is the actual property value which may or may not be null.</param>
		/// <returns>The value of the property (may be null), or null if the property itself was not found due to one of its parent properties being null.</returns>
		internal object ReadEntryPropertyValue(
			EntryPropertiesValueCache epmValueCache, 
			ResourceType resourceType,
			DataServiceMetadataProviderWrapper metadata,
			out bool nullOnParentProperty)
		{
			DebugUtils.CheckNoExternalCallers();
			Debug.Assert(this.propertyValuePath != null, "The propertyValuePath should have been initialized by now.");
			Debug.Assert(this.propertyValuePath.Length > 0, "The propertyValuePath must not be empty for an entry property.");
			Debug.Assert(resourceType != null, "resourceType != null");

			// TODO - verify that we actually need the internal property PropertyValuePath
			// TODO - It might be possible to avoid the "value" type checks below if we do property value validation based on the resource type
			return this.ReadPropertyValue(
				epmValueCache.EntryProperties,
				0,
				resourceType,
				metadata,
				epmValueCache,
				out nullOnParentProperty);
		}
Beispiel #10
0
        /// <summary>
        /// Write the content of the given entry.
        /// </summary>
        /// <param name="entry">The entry for which to write properties.</param>
        /// <param name="entryType">The <see cref="ResourceType"/> of the entry (or null if not metadata is available).</param>
        /// <param name="propertiesValueCache">The cache of properties.</param>
        /// <param name="rootSourcePathSegment">The root of the EPM source tree, if there's an EPM applied.</param>
        private void WriteEntryContent(ODataEntry entry, ResourceType entryType, EntryPropertiesValueCache propertiesValueCache, EpmSourcePathSegment rootSourcePathSegment)
        {
            Debug.Assert(entry != null, "entry != null");
            Debug.Assert(propertiesValueCache != null, "propertiesValueCache != null");

            ODataMediaResource mediaResource = entry.MediaResource;

            if (mediaResource == null)
            {
                // <content type="application/xml">
                this.writer.WriteStartElement(
                    AtomConstants.AtomNamespacePrefix,
                    AtomConstants.AtomContentElementName,
                    AtomConstants.AtomNamespace);

                this.writer.WriteAttributeString(
                    AtomConstants.AtomTypeAttributeName,
                    MimeConstants.MimeApplicationXml);

                // <m:properties>
                // we always write the <m:properties> element even if there are no properties
                this.writer.WriteStartElement(
                    AtomConstants.ODataMetadataNamespacePrefix,
                    AtomConstants.AtomPropertiesElementName,
                    AtomConstants.ODataMetadataNamespace);

                ODataAtomWriterUtils.WriteProperties(
                    this.writer,
                    this.MetadataProvider,
                    entryType,
                    propertiesValueCache.EntryProperties,
                    this.Version,
                    false,
                    propertiesValueCache,
                    rootSourcePathSegment);

                // </m:properties>
                this.writer.WriteEndElement();

                // </content>
                this.writer.WriteEndElement();
            }
            else
            {
                Uri mediaEditLink = mediaResource.EditLink;
                if (mediaEditLink != null)
                {
                    // <link rel="edit-media" href="href" />
                    this.writer.WriteStartElement(
                        AtomConstants.AtomNamespacePrefix,
                        AtomConstants.AtomLinkElementName,
                        AtomConstants.AtomNamespace);

                    this.writer.WriteAttributeString(
                        AtomConstants.AtomLinkRelationAttributeName,
                        AtomConstants.AtomEditMediaRelationAttributeValue);

                    this.writer.WriteAttributeString(
                        AtomConstants.AtomHRefAttributeName,
                        AtomUtils.ToUrlAttributeValue(mediaEditLink, this.BaseUri));

                    string mediaETag = mediaResource.ETag;
                    if (mediaETag != null)
                    {
                        this.writer.WriteAttributeString(
                            AtomConstants.ODataMetadataNamespacePrefix,
                            AtomConstants.ODataETagAttributeName,
                            AtomConstants.ODataMetadataNamespace,
                            mediaETag);
                    }

                    // </link>
                    this.writer.WriteEndElement();
                }

                Debug.Assert(mediaEditLink != null || mediaResource.ETag == null, "The default stream edit link and etag should have been validated by now.");

                // <content type="type" src="src">
                this.writer.WriteStartElement(
                    AtomConstants.AtomNamespacePrefix,
                    AtomConstants.AtomContentElementName,
                    AtomConstants.AtomNamespace);

                Debug.Assert(!string.IsNullOrEmpty(mediaResource.ContentType), "The default stream content type should have been validated by now.");
                this.writer.WriteAttributeString(
                    AtomConstants.AtomTypeAttributeName,
                    mediaResource.ContentType);

                Debug.Assert(mediaResource.ReadLink != null, "The default stream read link should have been validated by now.");
                this.writer.WriteAttributeString(
                    AtomConstants.MediaLinkEntryContentSourceAttributeName,
                    AtomUtils.ToUrlAttributeValue(mediaResource.ReadLink, this.BaseUri));

                // </content>
                this.writer.WriteEndElement();

                // <m:properties>
                // we always write the <m:properties> element even if there are no properties
                this.writer.WriteStartElement(
                    AtomConstants.ODataMetadataNamespacePrefix,
                    AtomConstants.AtomPropertiesElementName,
                    AtomConstants.ODataMetadataNamespace);

                ODataAtomWriterUtils.WriteProperties(
                    this.writer,
                    this.MetadataProvider,
                    entryType,
                    propertiesValueCache.EntryProperties,
                    this.Version,
                    false,
                    propertiesValueCache,
                    rootSourcePathSegment);

                // </m:properties>
                this.writer.WriteEndElement();
            }
        }
        /// <summary>
        /// Writes the syndication part of EPM for an entry into ATOM metadata OM.
        /// </summary>
        /// <param name="epmTargetTree">The EPM target tree to use.</param>
        /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param>
        /// <param name="resourceType">The resource type of the entry.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <param name="version">The version of OData protocol to use.</param>
        /// <returns>The ATOM metadata OM with the EPM values populated.</returns>
        internal static AtomEntryMetadata WriteEntryEpm(
            EpmTargetTree epmTargetTree,
            EntryPropertiesValueCache epmValueCache,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata,
            ODataVersion version)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(epmTargetTree != null, "epmTargetTree != null");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");
            Debug.Assert(resourceType != null, "For any EPM to exist the metadata must be available.");

            // If there are no syndication mappings, just return null.
            EpmTargetPathSegment syndicationRootSegment = epmTargetTree.SyndicationRoot;

            Debug.Assert(syndicationRootSegment != null, "EPM Target tree must always have syndication root.");
            if (syndicationRootSegment.SubSegments.Count == 0)
            {
                return(null);
            }

            AtomEntryMetadata entryMetadata = new AtomEntryMetadata();

            foreach (EpmTargetPathSegment targetSegment in syndicationRootSegment.SubSegments)
            {
                if (targetSegment.IsMultiValueProperty)
                {
                    Debug.Assert(
                        targetSegment.EpmInfo != null && targetSegment.EpmInfo.Attribute != null,
                        "MultiValue property target segment must have EpmInfo and the Epm Attribute.");

                    ODataVersionChecker.CheckMultiValueProperties(version, targetSegment.EpmInfo.Attribute.SourcePath);

                    // WriteMultiValueEpm(entry, targetSegment, epmValueCache);
                    throw new NotImplementedException();
                }
                else if (targetSegment.HasContent)
                {
                    EntityPropertyMappingInfo epmInfo = targetSegment.EpmInfo;
                    Debug.Assert(
                        epmInfo != null && epmInfo.Attribute != null,
                        "If the segment has content it must have EpmInfo which in turn must have the Epm attribute");

                    bool   nullOnParentProperty;
                    object propertyValue     = epmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty);
                    string textPropertyValue = EpmWriterUtils.GetPropertyValueAsText(propertyValue);

                    switch (epmInfo.Attribute.TargetSyndicationItem)
                    {
                    case SyndicationItemProperty.Updated:
                        entryMetadata.Updated = CreateDateTimeValue(propertyValue, SyndicationItemProperty.Updated, version);
                        break;

                    case SyndicationItemProperty.Published:
                        entryMetadata.Published = CreateDateTimeValue(propertyValue, SyndicationItemProperty.Published, version);
                        break;

                    case SyndicationItemProperty.Rights:
                        entryMetadata.Rights = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version);
                        break;

                    case SyndicationItemProperty.Summary:
                        entryMetadata.Summary = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version);
                        break;

                    case SyndicationItemProperty.Title:
                        entryMetadata.Title = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version);
                        break;

                    default:
                        throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WriteEntryEpm_ContentTarget));
                    }
                }
                else if (targetSegment.SegmentName == AtomConstants.AtomAuthorElementName)
                {
                    AtomPersonMetadata authorMetadata = WritePersonEpm(targetSegment, epmValueCache, resourceType, metadata);

                    Debug.Assert(entryMetadata.Authors == null, "Found two mappings to author, that is invalid.");
                    if (authorMetadata != null)
                    {
                        entryMetadata.Authors = CreateSinglePersonList(authorMetadata);
                    }
                }
                else if (targetSegment.SegmentName == AtomConstants.AtomContributorElementName)
                {
                    AtomPersonMetadata contributorMetadata = WritePersonEpm(targetSegment, epmValueCache, resourceType, metadata);

                    Debug.Assert(entryMetadata.Contributors == null, "Found two mappings to contributor, that is invalid.");
                    if (contributorMetadata != null)
                    {
                        entryMetadata.Contributors = CreateSinglePersonList(contributorMetadata);
                    }
                }
                else if (targetSegment.SegmentName == AtomConstants.AtomLinkElementName)
                {
                    AtomLinkMetadata linkMetadata = new AtomLinkMetadata();
                    //// WriteLinkEpm(entry, targetSegment, epmValueCache);

                    Debug.Assert(targetSegment.CriteriaValue != null, "Mapping to link must be conditional.");
                    linkMetadata.Relation = targetSegment.CriteriaValue;
                    List <AtomLinkMetadata> links;
                    if (entryMetadata.Links == null)
                    {
                        links = new List <AtomLinkMetadata>();
                        entryMetadata.Links = links;
                    }
                    else
                    {
                        links = entryMetadata.Links as List <AtomLinkMetadata>;
                        Debug.Assert(links != null, "AtomEntryMetadata.Links must be of type List<AtomLinkMetadata> since we create it like that.");
                    }

                    links.Add(linkMetadata);

                    throw new NotImplementedException();
                }
                else if (targetSegment.SegmentName == AtomConstants.AtomCategoryElementName)
                {
                    AtomCategoryMetadata categoryMetadata = new AtomCategoryMetadata();
                    //// WriteCategoryEpm(entry, targetSegment, epmValueCache)

                    Debug.Assert(targetSegment.CriteriaValue != null, "Mapping to category must be conditional.");
                    categoryMetadata.Scheme = targetSegment.CriteriaValue;
                    List <AtomCategoryMetadata> categories;
                    if (entryMetadata.Categories == null)
                    {
                        categories = new List <AtomCategoryMetadata>();
                        entryMetadata.Categories = categories;
                    }
                    else
                    {
                        categories = entryMetadata.Links as List <AtomCategoryMetadata>;
                        Debug.Assert(categories != null, "AtomEntryMetadata.Categories must be of type List<AtomCategoryMetadata> since we create it like that.");
                    }

                    categories.Add(categoryMetadata);

                    throw new NotImplementedException();
                }
                else
                {
                    throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WriteEntryEpm_TargetSegment));
                }
            }

            return(entryMetadata);
        }
        /// <summary>
        /// Writes EPM value to a person construct (author or contributor).
        /// </summary>
        /// <param name="targetSegment">The target segment which points to either author or contributor element.</param>
        /// <param name="epmValueCache">The EPM value cache to use to get property values.</param>
        /// <param name="resourceType">The resource type of the entry being processed.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <returns>The person metadata or null if no person metadata should be written for this mapping.</returns>
        private static AtomPersonMetadata WritePersonEpm(
            EpmTargetPathSegment targetSegment,
            EntryPropertiesValueCache epmValueCache,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata)
        {
            Debug.Assert(targetSegment != null, "targetSegment != null");
            Debug.Assert(
                targetSegment.SegmentName == AtomConstants.AtomAuthorElementName || targetSegment.SegmentName == AtomConstants.AtomContributorElementName,
                "targetSegment must be author or contributor.");

            AtomPersonMetadata personMetadata = null;

            foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments)
            {
                Debug.Assert(subSegment.HasContent, "sub segment of author segment must have content, there are no subsegments which don't have content under author.");
                string textPropertyValue = GetEntryPropertyValueAsText(subSegment, epmValueCache, resourceType, metadata);

                if (textPropertyValue == null)
                {
                    // TODO: In Multi-Values or in V3 mapping nulls to author/contributor subelements is not legal since there's no way to express it.
                    // author/contributor subelements don't allow extension attributes, so we can't add the m:null attribute.
                    continue;
                }

                // Initialize the person element only if we actually need to write something to it.
                if (personMetadata == null)
                {
                    personMetadata = new AtomPersonMetadata();
                }

                Debug.Assert(subSegment.EpmInfo != null && subSegment.EpmInfo.Attribute != null, "The author subsegment must have EPM info and EPM attribute.");
                switch (subSegment.EpmInfo.Attribute.TargetSyndicationItem)
                {
                case SyndicationItemProperty.AuthorName:
                case SyndicationItemProperty.ContributorName:
                    personMetadata.Name = textPropertyValue;
                    break;

                case SyndicationItemProperty.AuthorEmail:
                case SyndicationItemProperty.ContributorEmail:
                    // TODO: Validate the email value. In V3 or in multi-values the email value must not be null and it must not be empty
                    // since the syndication API doesn't allow empty email values (see below) and it's questionable if ATOM allows it itself.

                    // In case the value is empty the syndication API will actually omit the email element from the payload
                    // we have to simulate that behavior here by not setting the property in that case.
                    if (textPropertyValue.Length > 0)
                    {
                        personMetadata.Email = textPropertyValue;
                    }

                    break;

                case SyndicationItemProperty.AuthorUri:
                case SyndicationItemProperty.ContributorUri:
                    // TODO: Validate the uri value. In V3 or in multi-values the uri value must not be null and it must not be empty
                    // since the syndication API doesn't allow empty uri values (see below) and it's questionable if ATOM allows it itself.

                    // In case the value is empty the syndication API will actually omit the uri element from the payload
                    // we have to simulate that behavior here by not setting the property in that case.
                    if (textPropertyValue.Length > 0)
                    {
                        personMetadata.UriFromEpm = textPropertyValue;
                    }

                    break;

                default:
                    throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WritePersonEpm));
                }
            }

            return(personMetadata);
        }
        /// <summary>
        /// Writes the syndication part of EPM for an entry into ATOM metadata OM.
        /// </summary>
        /// <param name="epmTargetTree">The EPM target tree to use.</param>
        /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param>
        /// <param name="resourceType">The resource type of the entry.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <param name="version">The version of OData protocol to use.</param>
        /// <returns>The ATOM metadata OM with the EPM values populated.</returns>
        internal static AtomEntryMetadata WriteEntryEpm(
            EpmTargetTree epmTargetTree, 
            EntryPropertiesValueCache epmValueCache, 
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata,
            ODataVersion version)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(epmTargetTree != null, "epmTargetTree != null");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");
            Debug.Assert(resourceType != null, "For any EPM to exist the metadata must be available.");

            // If there are no syndication mappings, just return null.
            EpmTargetPathSegment syndicationRootSegment = epmTargetTree.SyndicationRoot;
            Debug.Assert(syndicationRootSegment != null, "EPM Target tree must always have syndication root.");
            if (syndicationRootSegment.SubSegments.Count == 0)
            {
                return null;
            }

            AtomEntryMetadata entryMetadata = new AtomEntryMetadata();

            foreach (EpmTargetPathSegment targetSegment in syndicationRootSegment.SubSegments)
            {
                if (targetSegment.IsMultiValueProperty)
                {
                    Debug.Assert(
                        targetSegment.EpmInfo != null && targetSegment.EpmInfo.Attribute != null,
                        "MultiValue property target segment must have EpmInfo and the Epm Attribute.");
                    
                    ODataVersionChecker.CheckMultiValueProperties(version, targetSegment.EpmInfo.Attribute.SourcePath);

                    // WriteMultiValueEpm(entry, targetSegment, epmValueCache);
                    throw new NotImplementedException();
                }
                else if (targetSegment.HasContent)
                {
                    EntityPropertyMappingInfo epmInfo = targetSegment.EpmInfo;
                    Debug.Assert(
                        epmInfo != null && epmInfo.Attribute != null, 
                        "If the segment has content it must have EpmInfo which in turn must have the Epm attribute");

                    bool nullOnParentProperty;
                    object propertyValue = epmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty);
                    string textPropertyValue = EpmWriterUtils.GetPropertyValueAsText(propertyValue);

                    switch (epmInfo.Attribute.TargetSyndicationItem)
                    {
                        case SyndicationItemProperty.Updated:
                            entryMetadata.Updated = CreateDateTimeValue(propertyValue, SyndicationItemProperty.Updated, version);
                            break;
                        case SyndicationItemProperty.Published:
                            entryMetadata.Published = CreateDateTimeValue(propertyValue, SyndicationItemProperty.Published, version);
                            break;
                        case SyndicationItemProperty.Rights:
                            entryMetadata.Rights = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version);
                            break;
                        case SyndicationItemProperty.Summary:
                            entryMetadata.Summary = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version);
                            break;
                        case SyndicationItemProperty.Title:
                            entryMetadata.Title = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version);
                            break;
                        default:
                            throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WriteEntryEpm_ContentTarget));
                    }
                }
                else if (targetSegment.SegmentName == AtomConstants.AtomAuthorElementName)
                {
                    AtomPersonMetadata authorMetadata = WritePersonEpm(targetSegment, epmValueCache, resourceType, metadata);

                    Debug.Assert(entryMetadata.Authors == null, "Found two mappings to author, that is invalid.");
                    if (authorMetadata != null)
                    {
                        entryMetadata.Authors = CreateSinglePersonList(authorMetadata);
                    }
                }
                else if (targetSegment.SegmentName == AtomConstants.AtomContributorElementName)
                {
                    AtomPersonMetadata contributorMetadata = WritePersonEpm(targetSegment, epmValueCache, resourceType, metadata);

                    Debug.Assert(entryMetadata.Contributors == null, "Found two mappings to contributor, that is invalid.");
                    if (contributorMetadata != null)
                    {
                        entryMetadata.Contributors = CreateSinglePersonList(contributorMetadata);
                    }
                }
                else if (targetSegment.SegmentName == AtomConstants.AtomLinkElementName)
                {
                    AtomLinkMetadata linkMetadata = new AtomLinkMetadata();
                    //// WriteLinkEpm(entry, targetSegment, epmValueCache);

                    Debug.Assert(targetSegment.CriteriaValue != null, "Mapping to link must be conditional.");
                    linkMetadata.Relation = targetSegment.CriteriaValue;
                    List<AtomLinkMetadata> links;
                    if (entryMetadata.Links == null)
                    {
                        links = new List<AtomLinkMetadata>();
                        entryMetadata.Links = links;
                    }
                    else
                    {
                        links = entryMetadata.Links as List<AtomLinkMetadata>;
                        Debug.Assert(links != null, "AtomEntryMetadata.Links must be of type List<AtomLinkMetadata> since we create it like that.");
                    }

                    links.Add(linkMetadata);

                    throw new NotImplementedException();
                }
                else if (targetSegment.SegmentName == AtomConstants.AtomCategoryElementName)
                {
                    AtomCategoryMetadata categoryMetadata = new AtomCategoryMetadata();
                    //// WriteCategoryEpm(entry, targetSegment, epmValueCache)

                    Debug.Assert(targetSegment.CriteriaValue != null, "Mapping to category must be conditional.");
                    categoryMetadata.Scheme = targetSegment.CriteriaValue;
                    List<AtomCategoryMetadata> categories;
                    if (entryMetadata.Categories == null)
                    {
                        categories = new List<AtomCategoryMetadata>();
                        entryMetadata.Categories = categories;
                    }
                    else
                    {
                        categories = entryMetadata.Links as List<AtomCategoryMetadata>;
                        Debug.Assert(categories != null, "AtomEntryMetadata.Categories must be of type List<AtomCategoryMetadata> since we create it like that.");
                    }

                    categories.Add(categoryMetadata);

                    throw new NotImplementedException();
                }
                else
                {
                    throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WriteEntryEpm_TargetSegment));
                }
            }

            return entryMetadata;
        }
        /// <summary>
        /// Writes EPM value to a person construct (author or contributor).
        /// </summary>
        /// <param name="targetSegment">The target segment which points to either author or contributor element.</param>
        /// <param name="epmValueCache">The EPM value cache to use to get property values.</param>
        /// <param name="resourceType">The resource type of the entry being processed.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <returns>The person metadata or null if no person metadata should be written for this mapping.</returns>
        private static AtomPersonMetadata WritePersonEpm(
            EpmTargetPathSegment targetSegment, 
            EntryPropertiesValueCache epmValueCache, 
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata)
        {
            Debug.Assert(targetSegment != null, "targetSegment != null");
            Debug.Assert(
                targetSegment.SegmentName == AtomConstants.AtomAuthorElementName || targetSegment.SegmentName == AtomConstants.AtomContributorElementName, 
                "targetSegment must be author or contributor.");

            AtomPersonMetadata personMetadata = null;
            foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments)
            {
                Debug.Assert(subSegment.HasContent, "sub segment of author segment must have content, there are no subsegments which don't have content under author.");
                string textPropertyValue = GetEntryPropertyValueAsText(subSegment, epmValueCache, resourceType, metadata);

                if (textPropertyValue == null)
                {
                    // TODO: In Multi-Values or in V3 mapping nulls to author/contributor subelements is not legal since there's no way to express it.
                    // author/contributor subelements don't allow extension attributes, so we can't add the m:null attribute.
                    continue;
                }

                // Initialize the person element only if we actually need to write something to it.
                if (personMetadata == null)
                {
                    personMetadata = new AtomPersonMetadata();
                }

                Debug.Assert(subSegment.EpmInfo != null && subSegment.EpmInfo.Attribute != null, "The author subsegment must have EPM info and EPM attribute.");
                switch (subSegment.EpmInfo.Attribute.TargetSyndicationItem)
                {
                    case SyndicationItemProperty.AuthorName:
                    case SyndicationItemProperty.ContributorName:
                        personMetadata.Name = textPropertyValue;
                        break;
                    case SyndicationItemProperty.AuthorEmail:
                    case SyndicationItemProperty.ContributorEmail:
                        // TODO: Validate the email value. In V3 or in multi-values the email value must not be null and it must not be empty
                        // since the syndication API doesn't allow empty email values (see below) and it's questionable if ATOM allows it itself.

                        // In case the value is empty the syndication API will actually omit the email element from the payload
                        // we have to simulate that behavior here by not setting the property in that case.
                        if (textPropertyValue.Length > 0)
                        {
                            personMetadata.Email = textPropertyValue;
                        }

                        break;
                    case SyndicationItemProperty.AuthorUri:
                    case SyndicationItemProperty.ContributorUri:
                        // TODO: Validate the uri value. In V3 or in multi-values the uri value must not be null and it must not be empty
                        // since the syndication API doesn't allow empty uri values (see below) and it's questionable if ATOM allows it itself.

                        // In case the value is empty the syndication API will actually omit the uri element from the payload
                        // we have to simulate that behavior here by not setting the property in that case.
                        if (textPropertyValue.Length > 0)
                        {
                            personMetadata.UriFromEpm = textPropertyValue;
                        }

                        break;
                    default:
                        throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WritePersonEpm));
                }
            }

            return personMetadata;
        }
        /// <summary>
        /// Given a target segment the method returns the text value of the property mapped to that segment to be used in EPM.
        /// </summary>
        /// <param name="targetSegment">The target segment to read the value for.</param>
        /// <param name="epmValueCache">The entry EPM value cache to use.</param>
        /// <param name="resourceType">The resource type of the entry being processed.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <returns>The test representation of the value, or the method throws if the text representation was not possible to obtain.</returns>
        private static string GetEntryPropertyValueAsText(
            EpmTargetPathSegment targetSegment, 
            EntryPropertiesValueCache epmValueCache, 
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata)
        {
            Debug.Assert(targetSegment != null, "targetSegment != null");
            Debug.Assert(targetSegment.HasContent, "The target segment to read property for must have content.");
            Debug.Assert(targetSegment.EpmInfo != null, "The EPM info must be available on the target segment to read its property.");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");
            Debug.Assert(resourceType != null, "resourceType != null");

            bool nullOnParentProperty;
            object propertyValue = targetSegment.EpmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty);
            return EpmWriterUtils.GetPropertyValueAsText(propertyValue);
        }
        /// <summary>
        /// Writes an EPM attribute target.
        /// </summary>
        /// <param name="writer">The writer to write to.</param>
        /// <param name="targetSegment">The target segment describing the attribute to write.</param>
        /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param>
        /// <param name="resourceType">The resource type of the entry.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <param name="alreadyDeclaredPrefix">The name of the prefix if it was already declared.</param>
        private static void WriteAttributeEpm(
            XmlWriter writer, 
            EpmTargetPathSegment targetSegment, 
            EntryPropertiesValueCache epmValueCache, 
            ResourceType resourceType, 
            DataServiceMetadataProviderWrapper metadata,
            ref string alreadyDeclaredPrefix)
        {
            Debug.Assert(writer != null, "writer != null");
            Debug.Assert(targetSegment != null && targetSegment.IsAttribute, "Only attribute target segments are supported by this method.");
            Debug.Assert(targetSegment.HasContent, "Attribute target segments must have content.");

            string textPropertyValue = GetEntryPropertyValueAsText(targetSegment, epmValueCache, resourceType, metadata);
            Debug.Assert(textPropertyValue != null, "Text value of a property mapped to attribute must not be null, the GetEntryPropertyValueAsText should take care of that.");

            // If the prefix is null, the WCF DS will still write it as the default namespace, so we need it to be an empty string.
            string attributePrefix = targetSegment.SegmentNamespacePrefix ?? string.Empty;
            writer.WriteAttributeString(attributePrefix, targetSegment.AttributeName, targetSegment.SegmentNamespaceUri, textPropertyValue);

            // Write out the declaration explicitely only if the prefix is not empty (just like the WCF DS does)
            if (attributePrefix.Length > 0)
            {
                WriteNamespaceDeclaration(writer, targetSegment, ref alreadyDeclaredPrefix);
            }
        }
        /// <summary>
        /// Given a target segment the method returns the text value of the property mapped to that segment to be used in EPM.
        /// </summary>
        /// <param name="targetSegment">The target segment to read the value for.</param>
        /// <param name="epmValueCache">The entry EPM value cache to use.</param>
        /// <param name="resourceType">The resource type of the entry being processed.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <returns>The test representation of the value, or the method throws if the text representation was not possible to obtain.</returns>
        private static string GetEntryPropertyValueAsText(
            EpmTargetPathSegment targetSegment, 
            EntryPropertiesValueCache epmValueCache, 
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata)
        {
            Debug.Assert(targetSegment != null, "targetSegment != null");
            Debug.Assert(targetSegment.HasContent, "The target segment to read property for must have content.");
            Debug.Assert(targetSegment.EpmInfo != null, "The EPM info must be available on the target segment to read its property.");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");
            Debug.Assert(resourceType != null, "resourceType != null");

            bool nullOnParentProperty;
            object propertyValue = targetSegment.EpmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty);
            if (propertyValue == null)
            {
                // TODO: In V3 when we use new format for null values using the m:null attribute we need to check here
                // if we're mapping into an attribute, in which case we should fail (can't write null to attribute in V3)
                // or if we're writing into element return true null, to use the m:null attribute.
                // In V2 nulls are written out as empty string always (and they're written into content as well)
                return string.Empty;
            }
            else
            {
                return EpmWriterUtils.GetPropertyValueAsText(propertyValue);
            }
        }
        /// <summary>
        /// Finish writing an entry.
        /// </summary>
        /// <param name="entry">The entry to write.</param>
        protected override void EndEntry(ODataEntry entry)
        {
            Debug.Assert(entry != null, "entry != null");

            string typeName = entry.TypeName;
            ResourceType entryType = ValidationUtils.ValidateTypeName(this.MetadataProvider, typeName, ResourceTypeKind.EntityType, false);

            // Initialize the property value cache and cache the entry properties.
            EntryPropertiesValueCache propertyValueCache = new EntryPropertiesValueCache(entry);
            EpmResourceTypeAnnotation epmResourceTypeAnnotation = null;
            if (entryType != null)
            {
                Debug.Assert(entryType.IsReadOnly, "The resource must be read-only to be applied to an entry.");
                entryType.EnsureEpmAvailability();
                epmResourceTypeAnnotation = entryType.Epm();
            }

            // <atom:id>idValue</atom:id>
            // NOTE: do not generate a relative Uri for the ID; it is independent of xml:base
            ODataAtomWriterUtils.WriteElementWithTextContent(
                this.writer, 
                AtomConstants.AtomNamespacePrefix, 
                AtomConstants.AtomIdElementName, 
                AtomConstants.AtomNamespace, 
                entry.Id);

            // <category term="type" scheme="odatascheme"/>
            // If no type information is provided, don't include the category element for type at all
            // NOTE: the validation of the type name happened at the beginning of this method.
            if (typeName != null)
            {
                ODataAtomWriterMetadataUtils.WriteCategory(this.writer, typeName, AtomConstants.ODataSchemeNamespace, null);
            }

            Uri editLink = entry.EditLink;
            if (editLink != null)
            {
                // <link rel="edit" title="Title" href="LinkHRef"/>
                ODataAtomWriterUtils.WriteReadOrEditLink(this.writer, this.BaseUri, editLink, AtomConstants.AtomEditRelationAttributeValue, typeName);
            }

            Uri readLink = entry.ReadLink;
            if (readLink != null)
            {
                // <link rel="self" title="Title" href="LinkHRef"/>
                ODataAtomWriterUtils.WriteReadOrEditLink(this.writer, this.BaseUri, readLink, AtomConstants.AtomSelfRelationAttributeValue, typeName);
            }

            // named streams
            IEnumerable<ODataMediaResource> namedStreams = entry.NamedStreams;
            if (namedStreams != null)
            {
                foreach (ODataMediaResource namedStream in namedStreams)
                {
                    ValidationUtils.ValidateNamedStream(namedStream, this.Version);

                    ODataAtomWriterUtils.WriteNamedStream(this.writer, this.BaseUri, namedStream);
                }
            }

            // association links
            IEnumerable<ODataAssociationLink> associationLinks = entry.AssociationLinks;
            if (associationLinks != null)
            {
                foreach (ODataAssociationLink associationLink in associationLinks)
                {
                    ValidationUtils.ValidateAssociationLink(associationLink, this.Version);

                    ODataAtomWriterUtils.WriteAssociationLink(this.writer, this.BaseUri, entry, associationLink);
                }
            }

            // write entry metadata including syndication EPM
            AtomEntryMetadata epmEntryMetadata = null;
            if (epmResourceTypeAnnotation != null)
            {
                ODataVersionChecker.CheckEntityPropertyMapping(this.Version, entryType);

                epmEntryMetadata = EpmSyndicationWriter.WriteEntryEpm(
                    epmResourceTypeAnnotation.EpmTargetTree,
                    propertyValueCache,
                    entryType,
                    this.MetadataProvider,
                    this.Version);
            }

            ODataAtomWriterMetadataUtils.WriteEntryMetadata(this.writer, this.BaseUri, entry, epmEntryMetadata);

            // write the content
            this.WriteEntryContent(entry, entryType, propertyValueCache, epmResourceTypeAnnotation == null ? null : epmResourceTypeAnnotation.EpmSourceTree.Root);

            // write custom EPM
            if (epmResourceTypeAnnotation != null)
            {
                EpmCustomWriter.WriteEntryEpm(
                    this.writer,
                    epmResourceTypeAnnotation.EpmTargetTree,
                    propertyValueCache,
                    entryType,
                    this.MetadataProvider);
            }

            // </entry>
            this.writer.WriteEndElement();
        }
        /// <summary>
        /// Write the content of the given entry.
        /// </summary>
        /// <param name="entry">The entry for which to write properties.</param>
        /// <param name="entryType">The <see cref="ResourceType"/> of the entry (or null if not metadata is available).</param>
        /// <param name="propertiesValueCache">The cache of properties.</param>
        /// <param name="rootSourcePathSegment">The root of the EPM source tree, if there's an EPM applied.</param>
        private void WriteEntryContent(ODataEntry entry, ResourceType entryType, EntryPropertiesValueCache propertiesValueCache, EpmSourcePathSegment rootSourcePathSegment)
        {
            Debug.Assert(entry != null, "entry != null");
            Debug.Assert(propertiesValueCache != null, "propertiesValueCache != null");

            ODataMediaResource mediaResource = entry.MediaResource;
            if (mediaResource == null)
            {
                // <content type="application/xml">
                this.writer.WriteStartElement(
                    AtomConstants.AtomNamespacePrefix,
                    AtomConstants.AtomContentElementName,
                    AtomConstants.AtomNamespace);

                this.writer.WriteAttributeString(
                    AtomConstants.AtomTypeAttributeName,
                    MimeConstants.MimeApplicationXml);

                // <m:properties>
                // we always write the <m:properties> element even if there are no properties
                this.writer.WriteStartElement(
                    AtomConstants.ODataMetadataNamespacePrefix,
                    AtomConstants.AtomPropertiesElementName,
                    AtomConstants.ODataMetadataNamespace);

                ODataAtomWriterUtils.WriteProperties(
                    this.writer, 
                    this.MetadataProvider,
                    entryType,
                    propertiesValueCache.EntryProperties,
                    this.Version,
                    false,
                    propertiesValueCache,
                    rootSourcePathSegment);

                // </m:properties>
                this.writer.WriteEndElement();

                // </content>
                this.writer.WriteEndElement();
            }
            else
            {
                Uri mediaEditLink = mediaResource.EditLink;
                if (mediaEditLink != null)
                {
                    // <link rel="edit-media" href="href" />
                    this.writer.WriteStartElement(
                        AtomConstants.AtomNamespacePrefix,
                        AtomConstants.AtomLinkElementName,
                        AtomConstants.AtomNamespace);

                    this.writer.WriteAttributeString(
                        AtomConstants.AtomLinkRelationAttributeName,
                        AtomConstants.AtomEditMediaRelationAttributeValue);

                    this.writer.WriteAttributeString(
                        AtomConstants.AtomHRefAttributeName,
                        AtomUtils.ToUrlAttributeValue(mediaEditLink, this.BaseUri));

                    string mediaETag = mediaResource.ETag;
                    if (mediaETag != null)
                    {
                        this.writer.WriteAttributeString(
                            AtomConstants.ODataMetadataNamespacePrefix,
                            AtomConstants.ODataETagAttributeName,
                            AtomConstants.ODataMetadataNamespace,
                            mediaETag);
                    }

                    // </link>
                    this.writer.WriteEndElement();
                }

                Debug.Assert(mediaEditLink != null || mediaResource.ETag == null, "The default stream edit link and etag should have been validated by now.");

                // <content type="type" src="src">
                this.writer.WriteStartElement(
                    AtomConstants.AtomNamespacePrefix,
                    AtomConstants.AtomContentElementName,
                    AtomConstants.AtomNamespace);

                Debug.Assert(!string.IsNullOrEmpty(mediaResource.ContentType), "The default stream content type should have been validated by now.");
                this.writer.WriteAttributeString(
                    AtomConstants.AtomTypeAttributeName,
                    mediaResource.ContentType);

                Debug.Assert(mediaResource.ReadLink != null, "The default stream read link should have been validated by now.");
                this.writer.WriteAttributeString(
                    AtomConstants.MediaLinkEntryContentSourceAttributeName,
                    AtomUtils.ToUrlAttributeValue(mediaResource.ReadLink, this.BaseUri));

                // </content>
                this.writer.WriteEndElement();

                // <m:properties>
                // we always write the <m:properties> element even if there are no properties
                this.writer.WriteStartElement(
                    AtomConstants.ODataMetadataNamespacePrefix,
                    AtomConstants.AtomPropertiesElementName,
                    AtomConstants.ODataMetadataNamespace);

                ODataAtomWriterUtils.WriteProperties(
                    this.writer, 
                    this.MetadataProvider,
                    entryType,
                    propertiesValueCache.EntryProperties, 
                    this.Version,
                    false,
                    propertiesValueCache,
                    rootSourcePathSegment);

                // </m:properties>
                this.writer.WriteEndElement();
            }
        }
Beispiel #20
0
        /// <summary>
        /// Finish writing an entry.
        /// </summary>
        /// <param name="entry">The entry to write.</param>
        protected override void EndEntry(ODataEntry entry)
        {
            Debug.Assert(entry != null, "entry != null");

            string       typeName  = entry.TypeName;
            ResourceType entryType = ValidationUtils.ValidateTypeName(this.MetadataProvider, typeName, ResourceTypeKind.EntityType, false);

            // Initialize the property value cache and cache the entry properties.
            EntryPropertiesValueCache propertyValueCache        = new EntryPropertiesValueCache(entry);
            EpmResourceTypeAnnotation epmResourceTypeAnnotation = null;

            if (entryType != null)
            {
                Debug.Assert(entryType.IsReadOnly, "The resource must be read-only to be applied to an entry.");
                entryType.EnsureEpmAvailability();
                epmResourceTypeAnnotation = entryType.Epm();
            }

            // <atom:id>idValue</atom:id>
            // NOTE: do not generate a relative Uri for the ID; it is independent of xml:base
            ODataAtomWriterUtils.WriteElementWithTextContent(
                this.writer,
                AtomConstants.AtomNamespacePrefix,
                AtomConstants.AtomIdElementName,
                AtomConstants.AtomNamespace,
                entry.Id);

            // <category term="type" scheme="odatascheme"/>
            // If no type information is provided, don't include the category element for type at all
            // NOTE: the validation of the type name happened at the beginning of this method.
            if (typeName != null)
            {
                ODataAtomWriterMetadataUtils.WriteCategory(this.writer, typeName, AtomConstants.ODataSchemeNamespace, null);
            }

            Uri editLink = entry.EditLink;

            if (editLink != null)
            {
                // <link rel="edit" title="Title" href="LinkHRef"/>
                ODataAtomWriterUtils.WriteReadOrEditLink(this.writer, this.BaseUri, editLink, AtomConstants.AtomEditRelationAttributeValue, typeName);
            }

            Uri readLink = entry.ReadLink;

            if (readLink != null)
            {
                // <link rel="self" title="Title" href="LinkHRef"/>
                ODataAtomWriterUtils.WriteReadOrEditLink(this.writer, this.BaseUri, readLink, AtomConstants.AtomSelfRelationAttributeValue, typeName);
            }

            // named streams
            IEnumerable <ODataMediaResource> namedStreams = entry.NamedStreams;

            if (namedStreams != null)
            {
                foreach (ODataMediaResource namedStream in namedStreams)
                {
                    ValidationUtils.ValidateNamedStream(namedStream, this.Version);

                    ODataAtomWriterUtils.WriteNamedStream(this.writer, this.BaseUri, namedStream);
                }
            }

            // association links
            IEnumerable <ODataAssociationLink> associationLinks = entry.AssociationLinks;

            if (associationLinks != null)
            {
                foreach (ODataAssociationLink associationLink in associationLinks)
                {
                    ValidationUtils.ValidateAssociationLink(associationLink, this.Version);

                    ODataAtomWriterUtils.WriteAssociationLink(this.writer, this.BaseUri, entry, associationLink);
                }
            }

            // write entry metadata including syndication EPM
            AtomEntryMetadata epmEntryMetadata = null;

            if (epmResourceTypeAnnotation != null)
            {
                ODataVersionChecker.CheckEntityPropertyMapping(this.Version, entryType);

                epmEntryMetadata = EpmSyndicationWriter.WriteEntryEpm(
                    epmResourceTypeAnnotation.EpmTargetTree,
                    propertyValueCache,
                    entryType,
                    this.MetadataProvider,
                    this.Version);
            }

            ODataAtomWriterMetadataUtils.WriteEntryMetadata(this.writer, this.BaseUri, entry, epmEntryMetadata);

            // write the content
            this.WriteEntryContent(entry, entryType, propertyValueCache, epmResourceTypeAnnotation == null ? null : epmResourceTypeAnnotation.EpmSourceTree.Root);

            // write custom EPM
            if (epmResourceTypeAnnotation != null)
            {
                EpmCustomWriter.WriteEntryEpm(
                    this.writer,
                    epmResourceTypeAnnotation.EpmTargetTree,
                    propertyValueCache,
                    entryType,
                    this.MetadataProvider);
            }

            // </entry>
            this.writer.WriteEndElement();
        }