File: System\Data\Services\Epm\EpmContentSerializer.cs
Project: ndp\fx\src\DataWeb\Server\System.Data.Services.csproj (System.Data.Services)
//---------------------------------------------------------------------
// <copyright file="EpmContentSerializer.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
// Class used for serializing EntityPropertyMappingAttribute content.
// </summary>
//
// @owner  Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Services.Common
{
#region Namespaces
    using System.Collections.Generic;
    using System.Data.Services.Internal;
    using System.Data.Services.Providers;
    using System.Data.Services.Serializers;
    using System.Diagnostics;
    using System.Linq;
    using System.ServiceModel.Syndication;
#endregion
 
    /// <summary>
    /// Publically visible interface of the Content serializer, acts as container for both Custom and Syndication content serializers
    /// </summary>
    internal sealed class EpmContentSerializer : IDisposable
    {
        /// <summary><see cref="ResourceType"/> for which this serializer works</summary>
        private readonly ResourceType resourceType;
 
        /// <summary>
        /// Syndication specific content serializer
        /// </summary>
        private readonly EpmSyndicationContentSerializer epmSyndicationSerializer;
 
        /// <summary>
        /// Custom content serializer
        /// </summary>
        private readonly EpmCustomContentSerializer epmCustomSerializer;
 
        /// <summary>
        /// Target syndication item to which we add serialized content
        /// </summary>
        private SyndicationItem targetItem;
 
        /// <summary>Collection of null valued properties for this current serialization</summary>
        private EpmNullValuedPropertyTree nullValuedProperties;
 
        /// <summary>
        /// Constructor creates contained serializers
        /// </summary>
        /// <param name="resourceType">Resource type being serialized</param>
        /// <param name="value">Instance of <paramref name="resourceType"/></param>
        /// <param name="targetItem">SyndicationItem to which content will be added</param>
        /// <param name="provider">Data Service provider used for rights verification.</param>
        internal EpmContentSerializer(ResourceType resourceType, Object value, SyndicationItem targetItem, DataServiceProviderWrapper provider)
        {
            this.resourceType = resourceType;
            this.targetItem = targetItem;
            this.nullValuedProperties = new EpmNullValuedPropertyTree(provider, value);
 
            if (this.NeedEpmSerialization)
            {
                this.epmSyndicationSerializer = new EpmSyndicationContentSerializer(this.resourceType.EpmTargetTree, value, targetItem, this.nullValuedProperties);
                this.epmCustomSerializer = new EpmCustomContentSerializer(this.resourceType.EpmTargetTree, value, targetItem, this.nullValuedProperties, provider);
            }
        }
 
        /// <summary>Does this type have any entity property mappings at all</summary>
        private bool NeedEpmSerialization
        {
            get
            {
                Debug.Assert(this.resourceType != null, "Must have a valid ResourceType");
                return this.resourceType.HasEntityPropertyMappings;
            }
        }
 
        /// <summary>
        /// Cleansup visitor specific state of the target tree
        /// </summary>
        public void Dispose()
        {
            if (this.NeedEpmSerialization)
            {
                Debug.Assert(this.epmSyndicationSerializer != null, "ResourceType with mapping implies a valid syndication content serializer");
                this.epmSyndicationSerializer.Dispose();
                Debug.Assert(this.epmCustomSerializer != null, "ResourceType with mapping implies a valid custom content serializer");
                this.epmCustomSerializer.Dispose();
            }
        }
 
        /// <summary>Delegates to each of custom and syndication serializers for serializing content</summary>
        /// <param name="content">Content in which to write null valued properties</param>
        /// <param name="provider">Data Service provider used for rights verification.</param>
        internal void Serialize(DictionaryContent content, DataServiceProviderWrapper provider)
        {
            if (this.NeedEpmSerialization)
            {
                Debug.Assert(this.epmSyndicationSerializer != null, "ResourceType with mapping implies a valid syndication content serializer");
                this.epmSyndicationSerializer.Serialize(provider);
                Debug.Assert(this.epmCustomSerializer != null, "ResourceType with mapping implies a valid custom content serializer");
                this.epmCustomSerializer.Serialize(provider);
 
                this.nullValuedProperties.AddNullValuesToContent(content);
            }
            else
            {
                Debug.Assert(this.targetItem != null, "Must always have target content item");
                this.targetItem.Authors.Add(new SyndicationPerson(null, String.Empty, null));
            }
        }
 
        /// <summary>Tree representing null valued properties for an instance</summary>
        internal sealed class EpmNullValuedPropertyTree
        {
            /// <summary>Root of the tree</summary>
            private EpmNullValuedPropertyNode root;
 
            /// <summary>Provider wrapper instance</summary>
            private DataServiceProviderWrapper provider;
 
            /// <summary>Object whose properties we will read</summary>
            private object element;
 
            /// <summary>Constructor that creates the root of the tree</summary>
            /// <param name="provider">Provider wrapper instance</param>
            /// <param name="element">Object whose properties we will read</param>
            internal EpmNullValuedPropertyTree(DataServiceProviderWrapper provider, object element)
            {
                this.root = new EpmNullValuedPropertyNode { Name = null };
                this.provider = provider;
                this.element = element;
            }
 
            /// <summary>Adds a property to the null valued collection</summary>
            /// <param name="epmInfo">EpmInfo containing the property information such as path</param>
            internal void Add(EntityPropertyMappingInfo epmInfo)
            {
                Debug.Assert(epmInfo != null, "epmInfo != null");
                EpmNullValuedPropertyNode current = this.root;
                ResourceType currentType = epmInfo.DefiningType;
                object currentValue = this.element;
 
                // We are here because the epm path points to a null value. If the path is multiple level deep, we need to
                // know the first level the null value begins and we don't need to serialize deeper than that.
                // To serialize the complex properties correctly in the case they are not already in content, we also need
                // to find the type for each segment from root to the first segment that has the null property value.
                foreach (var segment in epmInfo.Attribute.SourcePath.Split('/'))
                {
                    EpmNullValuedPropertyNode child = current.Children.FirstOrDefault(c => c.Name == segment);
                    if (child != null)
                    {
                        // The current segment is already added to the tree, reuse it.
                        current = child;
                        currentValue = child.Element;
                        currentType = child.ResourceType;
                    }
                    else
                    {
                        EpmNullValuedPropertyNode newNode = new EpmNullValuedPropertyNode { Name = segment };
                        Debug.Assert(currentType != null, "currentType != null");
                        ResourceProperty property = currentType.TryResolvePropertyName(segment);
 
                        Debug.Assert(currentValue != null, "currentValue != null");
                        ProjectedWrapper projectedValue = currentValue as ProjectedWrapper;
                        if (projectedValue == null)
                        {
                            if (property != null)
                            {
                                currentValue = this.provider.GetPropertyValue(currentValue, property, currentType);
                                currentValue = currentValue == DBNull.Value ? null : currentValue;
                                currentType = property.ResourceType;
                            }
                            else
                            {
                                // Handle open property...
                                currentValue = this.provider.GetOpenPropertyValue(currentValue, segment);
                                currentValue = currentValue == DBNull.Value ? null : currentValue;
                                if (currentValue != null)
                                {
                                    // Get the type from the instance.
                                    currentType = this.provider.GetResourceType(currentValue);
                                }
                                else
                                {
                                    // We have a null open property at hand, we don't know its type.
                                    // Default the type to string so that we will omit the type name
                                    // and just write out null. i.e. <d:prop m:null='true'/>
                                    currentType = ResourceType.PrimitiveStringResourceType;
                                }
                            }
                        }
                        else
                        {
                            currentValue = projectedValue.GetProjectedPropertyValue(segment);
                            currentValue = currentValue == DBNull.Value ? null : currentValue;
 
                            if (property != null)
                            {
                                currentType = property.ResourceType;
                            }
                            else
                            {
                                // Handle open property...
                                if (currentValue == null)
                                {
                                    // We have a null open property at hand, we don't know its type.
                                    // Default the type to string so that we will omit the type name
                                    // and just write out null. i.e. <d:prop m:null='true'/>
                                    currentType = ResourceType.PrimitiveStringResourceType;
                                }
                                else
                                {
                                    projectedValue = currentValue as ProjectedWrapper;
                                    if (projectedValue != null)
                                    {
                                        // Get the type from the project wrapper.
                                        currentType = this.provider.TryResolveResourceType(projectedValue.ResourceTypeName);
                                    }
                                    else
                                    {
                                        // Get the type from the instance.
                                        currentType = this.provider.GetResourceType(currentValue);
                                    }
                                }
                            }
                        }
 
                        Debug.Assert(currentType != null, "currentType != null");
                        Debug.Assert(currentValue != DBNull.Value, "currentValue != DBNull.Value -- we have converted DBNull to null");
                        newNode.ResourceType = currentType;
                        newNode.Element = currentValue;
                        current.Children.Add(newNode);
                        current = newNode;
                    }
 
                    if (current.Element == null)
                    {
                        // If the current element is null, we don't need to go further since that is the obvious reason
                        // that the children properties are null.
                        break;
                    }
                }
            }
 
            /// <summary>Adds the null valued properties to the content section</summary>
            /// <param name="content">Content to which null properties are to be added</param>
            internal void AddNullValuesToContent(DictionaryContent content)
            {
                this.AddNullValuesToContent(this.root, content);
            }
 
            /// <summary>Adds the null valued properties to the content section of a syndication entry</summary>
            /// <param name="currentRoot">Current root node</param>
            /// <param name="currentContent">Current collection to which property is to be added</param>
            private void AddNullValuesToContent(EpmNullValuedPropertyNode currentRoot, DictionaryContent currentContent)
            {
                foreach (EpmNullValuedPropertyNode node in currentRoot.Children)
                {
                    bool found;
                    DictionaryContent c = currentContent.Lookup(node.Name, out found);
 
                    Debug.Assert(node.ResourceType != null, "node.ResourceType != null");
                    switch (node.ResourceType.ResourceTypeKind)
                    {
                        case ResourceTypeKind.ComplexType:
                            if (!found)
                            {
                                // If a complex property is not found in content, it is either not being projected
                                // or all of its properties are mapped and all of them have KeepInContent=false
                                Debug.Assert(c == null, "when look up not found, c should be null.");
                                if (node.Element != null)
                                {
                                    Debug.Assert(node.Children.Count > 0, "If the property represented by the current node is not null, there must be children nodes.");
 
                                    // The complex property is not null, but some of its descendant properties are null.
                                    // We need to serialize the type name of the complex property.
                                    c = new DictionaryContent();
                                    currentContent.Add(node.Name, node.ResourceType.FullName, c);
                                }
                                else
                                {
                                    Debug.Assert(node.Children.Count == 0, "If the property represented by the current node is not null, there must not be any children node.");
 
                                    // The complex property is null, we write out m:null='true'.
                                    currentContent.AddNull(node.ResourceType.FullName, node.Name);
                                }
                            }
 
                            if (c != null)
                            {
                                // Only add the children properties if the complex property is not null.
                                this.AddNullValuesToContent(node, c);
                            }
 
                            break;
 
                        case ResourceTypeKind.Primitive:
                            Debug.Assert(c == null, "DictionaryContent not expected for primitive properties.");
                            Debug.Assert(node.Element == null, "node.Element == null");
                            if (!found)
                            {
                                currentContent.AddNull(node.ResourceType.FullName, node.Name);
                            }
 
                            // if found, use the value in currentContent, we don't need to do anything here.
                            break;
 
                        case ResourceTypeKind.EntityType:
                            Debug.Assert(false, "We cannot map navigation properties with friendly feeds.");
                            break;
                    }
                }
            }
 
            /// <summary>
            /// Representation for a node in the <see cref="EpmNullValuedPropertyTree"/>
            /// </summary>
            private sealed class EpmNullValuedPropertyNode
            {
                /// <summary>Children of current node</summary>
                private List<EpmNullValuedPropertyNode> children;
 
                /// <summary>Name of the property</summary>
                internal String Name
                {
                    get;
                    set;
                }
 
                /// <summary>ResourceType corresponding to the node</summary>
                internal ResourceType ResourceType
                {
                    get;
                    set;
                }
 
                /// <summary>Object whose element we will read</summary>
                internal object Element
                {
                    get;
                    set;
                }
 
                /// <summary>Lazily creates the children collection and returns the collection</summary>
                internal ICollection<EpmNullValuedPropertyNode> Children
                {
                    get
                    {
                        if (this.children == null)
                        {
                            this.children = new List<EpmNullValuedPropertyNode>();
                        }
 
                        return this.children;
                    }
                }
            }
        }
    }
}