File: System\Data\Services\Providers\EntityPropertyMappingInfo.cs
Project: ndp\fx\src\DataWeb\Server\System.Data.Services.csproj (System.Data.Services)
//---------------------------------------------------------------------
// <copyright file="EntityPropertyMappingInfo.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      Manages the mapping information for EntityPropertyMappingAttributes
//      on a ResourceType.
// </summary>
//
// @owner  Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Services.Common
{
    using System.Diagnostics;
#if ASTORIA_CLIENT
    using System.Data.Services.Client;
    using System.Reflection;
    using ClientTypeOrResourceType_Alias = System.Data.Services.Client.ClientType;
    using TypeOrResourceType_Alias = System.Type;
#else
    using System.Data.Services.Providers;
    using ClientTypeOrResourceType_Alias = System.Data.Services.Providers.ResourceType;
    using TypeOrResourceType_Alias = System.Data.Services.Providers.ResourceType;
#endif
 
    /// <summary>
    /// Holds information needed during content serialization/deserialization for
    /// each EntityPropertyMappingAttribute
    /// </summary>
    [DebuggerDisplay("EntityPropertyMappingInfo {DefiningType}")]
    internal sealed class EntityPropertyMappingInfo
    {
        /// <summary>
        /// Private field backing Attribute property.
        /// </summary>
        private readonly EntityPropertyMappingAttribute attribute;
 
        /// <summary>
        /// Private field backing DefiningType property
        /// </summary>
        private readonly TypeOrResourceType_Alias definingType;
        
        /// <summary>
        /// Source property path in the segmented form.. Stored in order not to have to call attribute.SourcePath.Split('/') each time we want to read the property value.
        /// </summary>
        private readonly string[] segmentedSourcePath;
 
        /// <summary>
        /// Type whose property is to be read. This property is of ClientType type on the client and of ResourceType type on the server.
        /// </summary>
        private readonly ClientTypeOrResourceType_Alias actualPropertyType;
 
#if !ASTORIA_CLIENT
 
        /// <summary>
        /// Private field backing IsEFProvider property
        /// </summary>
        private readonly bool isEFProvider;
 
        /// <summary>
        /// Creates instance of EntityPropertyMappingInfo class.
        /// </summary>
        /// <param name="attribute">The <see cref="EntityPropertyMappingAttribute"/> corresponding to this object</param>
        /// <param name="definingType">Type the <see cref="EntityPropertyMappingAttribute"/> was defined on.</param>
        /// <param name="actualPropertyType">Type whose property is to be read. This can be different from defining type when inheritance is involved.</param>
        /// <param name="isEFProvider">Whether the current data source is an EF provider. Needed for error reporting.</param>
        public EntityPropertyMappingInfo(EntityPropertyMappingAttribute attribute, ResourceType definingType, ResourceType actualPropertyType, bool isEFProvider)
        {
            this.isEFProvider = isEFProvider;
#else
        /// <summary>
        /// Creates instance of EntityPropertyMappingInfo class.
        /// </summary>
        /// <param name="attribute">The <see cref="EntityPropertyMappingAttribute"/> corresponding to this object</param>
        /// <param name="definingType">Type the <see cref="EntityPropertyMappingAttribute"/> was defined on.</param>
        /// <param name="actualPropertyType">ClientType whose property is to be read.</param>
        public EntityPropertyMappingInfo(EntityPropertyMappingAttribute attribute, Type definingType, ClientType actualPropertyType)
        {
#endif
            Debug.Assert(attribute != null, "attribute != null");
            Debug.Assert(definingType != null, "definingType != null");
            Debug.Assert(actualPropertyType != null, "actualPropertyType != null");
 
            this.attribute = attribute;
            this.definingType = definingType;
            this.actualPropertyType = actualPropertyType;
 
            Debug.Assert(!string.IsNullOrEmpty(attribute.SourcePath), "Invalid source path");
            this.segmentedSourcePath = attribute.SourcePath.Split('/');
        }
 
        /// <summary>
        /// The <see cref="EntityPropertyMappingAttribute"/> corresponding to this object
        /// </summary>
        public EntityPropertyMappingAttribute Attribute 
        { 
            get { return this.attribute; }
        }
 
        /// <summary>
        /// Type that has the <see cref="EntityPropertyMappingAttribute"/>
        /// </summary>
        public TypeOrResourceType_Alias DefiningType
        {
            get { return this.definingType; }
        }
 
#if ASTORIA_CLIENT
        /// <summary>
        /// Given a source property path reads the property value from the resource type instance
        /// </summary>
        /// <param name="element">Client type instance.</param>
        /// <returns>Property value read from the client type instance. Possibly null.</returns>
        internal object ReadPropertyValue(object element)
        {
            return ReadPropertyValue(element, this.actualPropertyType, this.segmentedSourcePath, 0);
        }
 
        /// <summary>
        /// Given a source property path in segmented form, reads the property value from the resource type instance
        /// </summary>
        /// <param name="element">Client type instance.</param>
        /// <param name="resourceType">Client type whose property is to be read</param>
        /// <param name="srcPathSegments">Segmented source property path.</param>
        /// <param name="currentSegment">Index of current property name in <paramref name="srcPathSegments"/></param>
        /// <returns>Property value read from the client type instance. Possibly null.</returns>
        private static object ReadPropertyValue(object element, ClientType resourceType, string[] srcPathSegments, int currentSegment)
        {
            if (element == null || currentSegment == srcPathSegments.Length)
            {
                return element;
            }
            else
            {
                String srcPathPart = srcPathSegments[currentSegment];
 
                ClientType.ClientProperty resourceProperty = resourceType.GetProperty(srcPathPart, true);
                if (resourceProperty == null)
                {
                    throw Error.InvalidOperation(Strings.EpmSourceTree_InaccessiblePropertyOnType(srcPathPart, resourceType.ElementTypeName));
                }
 
                // If this is the last part of the path, then it has to be a primitive type otherwise should be a complex type
                if (resourceProperty.IsKnownType ^ (currentSegment == srcPathSegments.Length - 1))
                {
                    throw Error.InvalidOperation(!resourceProperty.IsKnownType ? Strings.EpmClientType_PropertyIsComplex(resourceProperty.PropertyName) :
                                                                                 Strings.EpmClientType_PropertyIsPrimitive(resourceProperty.PropertyName));
                }
 
                // o.Prop
                PropertyInfo pi = element.GetType().GetProperty(srcPathPart, BindingFlags.Instance | BindingFlags.Public);
                Debug.Assert(pi != null, "Cannot find property " + srcPathPart + "on type " + element.GetType().Name);
 
                return ReadPropertyValue(
                            pi.GetValue(element, null),
                            resourceProperty.IsKnownType ? null : ClientType.Create(resourceProperty.PropertyType),
                            srcPathSegments,
                            ++currentSegment);
            }
        }
#else
        /// <summary>Is the current data source an EF provider</summary>
        public bool IsEFProvider
        {
            get { return this.isEFProvider;  }
        }       
 
        /// <summary>
        /// Given a source property path reads the property value from the resource type instance.
        /// </summary>
        /// <param name="element">Resource type instance.</param>
        /// <param name="provider">Underlying data provider.</param>
        /// <returns>Property value read from the resource type instance. Possibly null.</returns>
        internal object ReadPropertyValue(object element, DataServiceProviderWrapper provider)
        {
            return ReadPropertyValue(element, provider, this.actualPropertyType, this.segmentedSourcePath, 0);
        }
 
        /// <summary>
        /// Given a source property path in the segmented form reads the property value from the resource type instance.
        /// </summary>
        /// <param name="element">Resource type instance.</param>
        /// <param name="provider">Underlying data provider.</param>
        /// <param name="resourceType">Resource type whose property is to be read.</param>
        /// <param name="srcPathSegments">Segmented source property path.</param>
        /// <param name="currentSegment">Index of current property name in <paramref name="srcPathSegments"/></param>
        /// <returns>Property value read from the resource type instance. Possibly null.</returns>
        private static object ReadPropertyValue(object element, DataServiceProviderWrapper provider, ResourceType resourceType, string[] srcPathSegments, int currentSegment)
        {
            if (element == null || currentSegment == srcPathSegments.Length)
            {
                return element;
            }
            else
            {
                String propertyName = srcPathSegments[currentSegment];
                ResourceProperty resourceProperty = resourceType != null ? resourceType.TryResolvePropertyName(propertyName) : null;
 
                if (resourceProperty != null)
                {
                    // If this is the last part of the path, then it has to be a primitive type otherwise should be a complex type
                    if (!resourceProperty.IsOfKind(currentSegment == srcPathSegments.Length - 1 ? ResourcePropertyKind.Primitive : ResourcePropertyKind.ComplexType))
                    {
                        throw new InvalidOperationException(Strings.EpmSourceTree_EndsWithNonPrimitiveType(propertyName));
                    }
                }
                else
                {
                    if (!(resourceType == null || resourceType.IsOpenType))
                    {
                        throw new InvalidOperationException(Strings.EpmSourceTree_InaccessiblePropertyOnType(propertyName, resourceType.Name));
                    }
 
                    // this is an open type resolve resourceType and try resolving resourceProperty
                    resourceType = WebUtil.GetNonPrimitiveResourceType(provider, element);
                    resourceProperty = resourceType.TryResolvePropertyName(propertyName);
                }
 
                Debug.Assert(resourceType != null, "resourceType != null");
                object propertyValue = WebUtil.GetPropertyValue(provider, element, resourceType, resourceProperty, resourceProperty == null ? propertyName : null);
 
                return ReadPropertyValue(
                    propertyValue,
                    provider,
                    resourceProperty != null ? resourceProperty.ResourceType : null,
                    srcPathSegments,
                    currentSegment + 1);
            }
        }
#endif
    }
}