File: System\Data\Services\Client\Epm\EpmSyndicationContentSerializer.cs
Project: ndp\fx\src\DataWeb\Client\System.Data.Services.Client.csproj (System.Data.Services.Client)
//---------------------------------------------------------------------
// <copyright file="EpmSyndicationContentSerializer.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
// Classes used for serializing EntityPropertyMappingAttribute content for
// syndication specific mappings
// </summary>
//
// @owner  Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Services.Client
{
    using System.Data.Services.Common;
    using System.Diagnostics;
    using System.Xml;
 
    /// <summary>
    /// Base visitor class for performing serialization of syndication specific content in the feed entry whose mapping
    /// is provided through EntityPropertyMappingAttributes
    /// </summary>
    internal sealed class EpmSyndicationContentSerializer : EpmContentSerializerBase, IDisposable
    {
        /// <summary>
        /// Is author mapping provided in the target tree
        /// </summary>
        private bool authorInfoPresent;
 
        /// <summary>
        /// Is updated mapping provided in the target tree
        /// </summary>
        private bool updatedPresent;
 
        /// <summary>
        /// Is author name provided in the target tree
        /// </summary>
        private bool authorNamePresent;
 
        /// <summary>
        /// Constructor initializes the base class be identifying itself as a syndication serializer
        /// </summary>
        /// <param name="tree">Target tree containing mapping information</param>
        /// <param name="element">Object to be serialized</param>
        /// <param name="target">SyndicationItem to which content will be added</param>
        internal EpmSyndicationContentSerializer(EpmTargetTree tree, object element, XmlWriter target)
            : base(tree, true, element, target)
        {
        }
 
        /// <summary>
        /// Used for housecleaning once every node has been visited, creates an author and updated syndication elements if none 
        /// have been created by the visitor
        /// </summary>
        public void Dispose()
        {
            this.CreateAuthor(true);
            this.CreateUpdated();
        }
 
        /// <summary>
        /// Override of the base Visitor method, which actually performs mapping search and serialization
        /// </summary>
        /// <param name="targetSegment">Current segment being checked for mapping</param>
        /// <param name="kind">Which sub segments to serialize</param>
        protected override void Serialize(EpmTargetPathSegment targetSegment, EpmSerializationKind kind)
        {
            if (targetSegment.HasContent)
            {
                EntityPropertyMappingInfo epmInfo = targetSegment.EpmInfo;
                Object propertyValue;
 
                try
                {
                    propertyValue = epmInfo.ReadPropertyValue(this.Element);
                }
                catch (System.Reflection.TargetInvocationException)
                {
                    throw;
                }
 
                String contentType;
                Action<String> contentWriter;
 
                switch (epmInfo.Attribute.TargetTextContentKind)
                {
                    case SyndicationTextContentKind.Html:
                        contentType = "html";
                        contentWriter = this.Target.WriteString;
                        break;
                    case SyndicationTextContentKind.Xhtml:
                        contentType = "xhtml";
                        contentWriter = this.Target.WriteRaw;
                        break;
                    default:
                        contentType = "text";
                        contentWriter = this.Target.WriteString;
                        break;
                }
 
                Action<String, bool, bool> textSyndicationWriter = (c, nonTextPossible, atomDateConstruct) =>
                {
                    this.Target.WriteStartElement(c, XmlConstants.AtomNamespace);
                    if (nonTextPossible)
                    {
                        this.Target.WriteAttributeString(XmlConstants.AtomTypeAttributeName, String.Empty, contentType);
                    }
 
                    // As per atomPub spec dateConstructs must contain valid datetime. Therefore we need to fill atom:updated/atom:published 
                    // field with something (e.g. DateTimeOffset.MinValue) if the user wants to map null to either of these fields. This will 
                    // satisfy protocol requirements and Syndication API. Note that the content will still contain information saying that the 
                    // mapped property is supposed to be null - therefore the server will know that the actual value sent by the user is null.
                    // For all other elements we can use empty string since the content is not validated.
                    String textPropertyValue = 
                        propertyValue != null   ? ClientConvert.ToString(propertyValue, atomDateConstruct) :
                        atomDateConstruct       ? ClientConvert.ToString(DateTime.MinValue, atomDateConstruct) : 
                        String.Empty;
 
                    contentWriter(textPropertyValue);
                    this.Target.WriteEndElement();
                };
 
                switch (epmInfo.Attribute.TargetSyndicationItem)
                {
                    case SyndicationItemProperty.AuthorEmail:
                    case SyndicationItemProperty.ContributorEmail:
                        textSyndicationWriter(XmlConstants.AtomEmailElementName, false, false);
                        break;
                    case SyndicationItemProperty.AuthorName:
                    case SyndicationItemProperty.ContributorName:
                        textSyndicationWriter(XmlConstants.AtomNameElementName, false, false);
                        this.authorNamePresent = true;
                        break;
                    case SyndicationItemProperty.AuthorUri:
                    case SyndicationItemProperty.ContributorUri:
                        textSyndicationWriter(XmlConstants.AtomUriElementName, false, false);
                        break;
                    case SyndicationItemProperty.Updated:
                        textSyndicationWriter(XmlConstants.AtomUpdatedElementName, false, true);
                        this.updatedPresent = true;
                        break;
                    case SyndicationItemProperty.Published:
                        textSyndicationWriter(XmlConstants.AtomPublishedElementName, false, true);
                        break;
                    case SyndicationItemProperty.Rights:
                        textSyndicationWriter(XmlConstants.AtomRightsElementName, true, false);
                        break;
                    case SyndicationItemProperty.Summary:
                        textSyndicationWriter(XmlConstants.AtomSummaryElementName, true, false);
                        break;
                    case SyndicationItemProperty.Title:
                        textSyndicationWriter(XmlConstants.AtomTitleElementName, true, false);
                        break;
                    default:
                        Debug.Assert(false, "Unhandled SyndicationItemProperty enum value - should never get here.");
                        break;
                }
            }
            else
            {
                if (targetSegment.SegmentName == XmlConstants.AtomAuthorElementName)
                {
                    this.CreateAuthor(false);
                    base.Serialize(targetSegment, kind);
                    this.FinishAuthor();
                }
                else if (targetSegment.SegmentName == XmlConstants.AtomContributorElementName)
                {
                    this.Target.WriteStartElement(XmlConstants.AtomContributorElementName, XmlConstants.AtomNamespace);
                    base.Serialize(targetSegment, kind);
                    this.Target.WriteEndElement();
                }
                else
                {
                    Debug.Assert(false, "Only authors and contributors have nested elements");
                }
            }
        }
 
        /// <summary>
        /// Creates a new author syndication specific object
        /// </summary>
        /// <param name="createNull">Whether to create a null author or a non-null author</param>
        private void CreateAuthor(bool createNull)
        {
            if (!this.authorInfoPresent)
            {
                if (createNull)
                {
                    this.Target.WriteStartElement(XmlConstants.AtomAuthorElementName, XmlConstants.AtomNamespace);
                    this.Target.WriteElementString(XmlConstants.AtomNameElementName, XmlConstants.AtomNamespace, String.Empty);
                    this.Target.WriteEndElement();
                }
                else
                {
                    this.Target.WriteStartElement(XmlConstants.AtomAuthorElementName, XmlConstants.AtomNamespace);
                }
 
                this.authorInfoPresent = true;
            }
        }
 
        /// <summary>
        /// Finish writing the author atom element
        /// </summary>
        private void FinishAuthor()
        {
            Debug.Assert(this.authorInfoPresent == true, "Must have already written the start element for author");
            if (this.authorNamePresent == false)
            {
                this.Target.WriteElementString(XmlConstants.AtomNameElementName, XmlConstants.AtomNamespace, String.Empty);
                this.authorNamePresent = true;
            }
 
            this.Target.WriteEndElement();
        }
 
        /// <summary>
        /// Ensures that mandatory atom:update element is creatd. The value of the created element is set to with value set to DateTime.UtcNow. 
        /// This method is used only if there are some EPM mappings for the given resource type but there is no mapping to atom:updated 
        /// element. Otherwise the element will be created during mapping handling or if there is no EPM mapping at all during the serialization. 
        /// </summary>
        private void CreateUpdated()
        {
            if (!this.updatedPresent)
            {
                this.Target.WriteElementString(XmlConstants.AtomUpdatedElementName, XmlConstants.AtomNamespace, XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.RoundtripKind));
            }
        }
    }
}