Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataWeb / Server / System / Data / Services / Providers / ResourceType.cs / 1305376 / ResourceType.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Contains information about a particular resource.
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services.Providers
{
#region Namespaces.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Services.Common;
using System.Data.Services.Parsing;
using System.Data.Services.Serializers;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
#endregion Namespaces.
/// Use this class to represent a DataService type (primitive, complex or entity).
[DebuggerDisplay("{Name}: {InstanceType}, {ResourceTypeKind}")]
public class ResourceType
{
#region Fields.
/// empty list of properties
internal static readonly ReadOnlyCollection EmptyProperties = new ReadOnlyCollection(new ResourceProperty[0]);
/// Primitive string resource type.
internal static readonly ResourceType PrimitiveStringResourceType = ResourceType.GetPrimitiveResourceType(typeof(string));
/// MethodInfo for object DataServiceProviderWrapper.GetPropertyValue(object target, ResourceProperty resourceProperty, ResourceType resourceType).
private static readonly MethodInfo GetPropertyValueMethodInfo = typeof(DataServiceProviderWrapper).GetMethod(
"GetPropertyValue",
WebUtil.PublicInstanceBindingFlags);
/// MethodInfo for object IProjectedResult.GetProjectedPropertyValue(this IProjectedResult value, string propertyName).
private static readonly MethodInfo IProjectedResultGetProjectedPropertyValueMethodInfo = typeof(IProjectedResult).GetMethod(
"GetProjectedPropertyValue",
WebUtil.PublicInstanceBindingFlags);
/// ResourceTypeKind for the type that this structure represents
private readonly ResourceTypeKind resourceTypeKind;
/// Reference to clr type that this resource represents
private readonly Type type;
/// Reference to base resource type
private readonly ResourceType baseType;
/// name of the resource.
private readonly string name;
/// full name of the resource.
private readonly string fullName;
/// Namespace for this type.
private readonly string namespaceName;
/// Whether this type is abstract.
private readonly bool abstractType;
/// Whether the resource type has open properties.
private bool isOpenType;
/// Whether the corresponding instance type actually represents this node's CLR type.
private bool canReflectOnInstanceType;
/// Cached delegate to create a new instance of this type.
private Func constructorDelegate;
/// Cached delegate to serialize parts of this resource into a dictionary.
private Action dictionarySerializerDelegate;
/// List of properties declared in this type (includes properties only defined in this type, not in the base type)
private IList propertiesDeclaredOnThisType;
/// List of all properties for this type (includes properties defined in the base type also)
private ReadOnlyCollection allProperties;
/// list of key properties for this type
private ReadOnlyCollection keyProperties;
/// list of etag properties for this type.
private ReadOnlyCollection etagProperties;
/// If ResourceProperty.CanReflectOnInstanceTypeProperty is true, we cache the PropertyInfo object.
private Dictionary propertyInfosDeclaredOnThisType = new Dictionary(ReferenceEqualityComparer.Instance);
/// EpmInfo for this
private EpmInfoPerResourceType epmInfo;
/// Indicates whether one of the base class of this resource type has EpmInfo.
private bool? basesHaveEpmInfo;
/// is true, if the type is set to readonly.
private bool isReadOnly;
/// True if the resource type includes a default stream
private bool isMediaLinkEntry;
/// True if the virtual load properties is already called, otherwise false.
private bool isLoadPropertiesMethodCalled;
#endregion Fields.
#region Constructors.
///
/// Constructs a new instance of Astoria type using the specified clr type
///
/// clr type that represents the flow format inside the Astoria runtime
/// kind of the resource type
/// base type of the resource type
/// Namespace name of the given resource type.
/// name of the given resource type.
/// whether the resource type is an abstract type or not.
public ResourceType(
Type instanceType,
ResourceTypeKind resourceTypeKind,
ResourceType baseType,
string namespaceName,
string name,
bool isAbstract)
: this(instanceType, baseType, namespaceName, name, isAbstract)
{
WebUtil.CheckArgumentNull(instanceType, "instanceType");
WebUtil.CheckStringArgumentNull(name, "name");
WebUtil.CheckResourceTypeKind(resourceTypeKind, "resourceTypeKind");
if (resourceTypeKind == ResourceTypeKind.Primitive)
{
throw new ArgumentException(Strings.ResourceType_InvalidValueForResourceTypeKind, "resourceTypeKind");
}
if (instanceType.IsValueType)
{
throw new ArgumentException(Strings.ResourceType_TypeCannotBeValueType, "instanceType");
}
this.resourceTypeKind = resourceTypeKind;
}
///
/// Constructs a new instance of Resource type for the given clr primitive type. This constructor must be called only for primitive types.
///
/// clr type representing the primitive type.
/// namespace of the primitive type.
/// name of the primitive type.
internal ResourceType(Type type, string namespaceName, string name)
: this(type, null, namespaceName, name, false)
{
Debug.Assert(WebUtil.IsPrimitiveType(type), "This constructor should be called only for primitive types");
this.resourceTypeKind = ResourceTypeKind.Primitive;
this.isReadOnly = true;
}
///
/// Constructs a new instance of Astoria type using the specified clr type
///
/// clr type from which metadata needs to be pulled
/// base type of the resource type
/// Namespace name of the given resource type.
/// name of the given resource type.
/// whether the resource type is an abstract type or not.
private ResourceType(
Type type,
ResourceType baseType,
string namespaceName,
string name,
bool isAbstract)
{
WebUtil.CheckArgumentNull(type, "type");
WebUtil.CheckArgumentNull(name, "name");
this.name = name;
this.namespaceName = namespaceName ?? string.Empty;
// This is to optimize the string property name in PlainXmlSerializer.WriteStartElementWithType function.
// Checking here is a fixed overhead, and the gain is every time we serialize a string property.
if (name == "String" && Object.ReferenceEquals(namespaceName, XmlConstants.EdmNamespace))
{
this.fullName = XmlConstants.EdmStringTypeName;
}
else
{
this.fullName = string.IsNullOrEmpty(namespaceName) ? name : namespaceName + "." + name;
}
this.type = type;
this.abstractType = isAbstract;
this.canReflectOnInstanceType = true;
if (baseType != null)
{
this.baseType = baseType;
}
}
#endregion Constructors.
#region Properties.
/// True if the resource type includes a default stream
public bool IsMediaLinkEntry
{
[DebuggerStepThrough]
get
{
return this.isMediaLinkEntry;
}
set
{
this.ThrowIfSealed();
if (this.resourceTypeKind != ResourceTypeKind.EntityType && value == true)
{
throw new InvalidOperationException(Strings.ReflectionProvider_HasStreamAttributeOnlyAppliesToEntityType(this.name));
}
this.isMediaLinkEntry = value;
}
}
/// Reference to clr type that this resource represents
public Type InstanceType
{
[DebuggerStepThrough]
get { return this.type; }
}
/// Reference to base resource type, if any
public ResourceType BaseType
{
[DebuggerStepThrough]
get { return this.baseType; }
}
/// ResourceTypeKind of this type
public ResourceTypeKind ResourceTypeKind
{
[DebuggerStepThrough]
get { return this.resourceTypeKind; }
}
/// Returns the list of properties for this type
public ReadOnlyCollection Properties
{
get
{
return this.InitializeProperties();
}
}
/// list of properties declared on this type
public ReadOnlyCollection PropertiesDeclaredOnThisType
{
get
{
ReadOnlyCollection readOnlyProperties = this.propertiesDeclaredOnThisType as ReadOnlyCollection;
if (readOnlyProperties == null)
{
// This method will call the virtual method, if that's not been called yet and add the list of properties
// returned by the virtual method to the properties collection.
this.GetPropertiesDeclaredOnThisType();
readOnlyProperties = new ReadOnlyCollection(this.propertiesDeclaredOnThisType ?? ResourceType.EmptyProperties);
if (!this.isReadOnly)
{
return readOnlyProperties;
}
// First try and validate the type. If that succeeds, then cache the results. otherwise we need to revert the results.
IList propertyCollection = this.propertiesDeclaredOnThisType;
this.propertiesDeclaredOnThisType = readOnlyProperties;
try
{
this.ValidateType();
}
catch (Exception)
{
this.propertiesDeclaredOnThisType = propertyCollection;
throw;
}
}
Debug.Assert(this.isReadOnly, "PropetiesDeclaredInThisType - at this point, the resource type must be readonly");
return readOnlyProperties;
}
}
/// Returns the list of key properties for this type, if this type is entity type.
public ReadOnlyCollection KeyProperties
{
get
{
if (this.keyProperties == null)
{
ResourceType rootType = this;
while (rootType.BaseType != null)
{
rootType = rootType.BaseType;
}
ReadOnlyCollection readOnlyKeyProperties;
if (rootType.Properties == null)
{
readOnlyKeyProperties = ResourceType.EmptyProperties;
}
else
{
List key = rootType.Properties.Where(p => p.IsOfKind(ResourcePropertyKind.Key)).ToList();
key.Sort(ResourceType.ResourcePropertyComparison);
readOnlyKeyProperties = new ReadOnlyCollection(key);
}
if (!this.isReadOnly)
{
return readOnlyKeyProperties;
}
this.keyProperties = readOnlyKeyProperties;
}
Debug.Assert(this.isReadOnly, "KeyProperties - at this point, the resource type must be readonly");
Debug.Assert(
(this.ResourceTypeKind != ResourceTypeKind.EntityType && this.keyProperties.Count == 0) ||
(this.ResourceTypeKind == ResourceTypeKind.EntityType && this.keyProperties.Count > 0),
"Entity type must have key properties and non-entity types cannot have key properties");
return this.keyProperties;
}
}
/// Returns the list of etag properties for this type.
public ReadOnlyCollection ETagProperties
{
get
{
if (this.etagProperties == null)
{
ReadOnlyCollection etag = new ReadOnlyCollection(this.Properties.Where(p => p.IsOfKind(ResourcePropertyKind.ETag)).ToList());
if (!this.isReadOnly)
{
return etag;
}
this.etagProperties = etag;
}
Debug.Assert(this.isReadOnly, "ETagProperties - at this point, the resource type must be readonly");
return this.etagProperties;
}
}
/// Gets the name of the resource.
public string Name
{
get { return this.name; }
}
/// Gets the fullname of the resource.
public string FullName
{
get { return this.fullName; }
}
/// Returns the namespace of this type.
public string Namespace
{
get { return this.namespaceName; }
}
/// Indicates whether this is an abstract type.
public bool IsAbstract
{
get { return this.abstractType; }
}
/// Indicates whether the resource type has open properties.
public bool IsOpenType
{
[DebuggerStepThrough]
get
{
return this.isOpenType;
}
set
{
this.ThrowIfSealed();
// Complex types can not be marked as open.
if (this.resourceTypeKind == ResourceTypeKind.ComplexType && value == true)
{
throw new InvalidOperationException(Strings.ResourceType_ComplexTypeCannotBeOpen(this.FullName));
}
this.isOpenType = value;
}
}
/// Whether the corresponding instance type actually represents this node's CLR type.
public bool CanReflectOnInstanceType
{
[DebuggerStepThrough]
get
{
return this.canReflectOnInstanceType;
}
set
{
this.ThrowIfSealed();
this.canReflectOnInstanceType = value;
}
}
///
/// PlaceHolder to hold custom state information about resource type.
///
public object CustomState
{
get;
set;
}
///
/// Returns true, if this resource type has been set to read only. Otherwise returns false.
///
public bool IsReadOnly
{
get { return this.isReadOnly; }
}
/// Cached delegate to create a new instance of this type.
internal Func ConstructorDelegate
{
get
{
if (this.constructorDelegate == null)
{
this.constructorDelegate = (Func)
WebUtil.CreateNewInstanceConstructor(this.InstanceType, this.FullName, typeof(object));
}
return this.constructorDelegate;
}
}
/// Cached delegate to serialize parts of this resource into a dictionary.
internal Action DictionarySerializerDelegate
{
get { return this.dictionarySerializerDelegate; }
set { this.dictionarySerializerDelegate = value; }
}
///
/// Do we have entity property mappings for this
///
internal bool HasEntityPropertyMappings
{
get
{
Debug.Assert(this.IsReadOnly, "Type must be read-only.");
if (this.epmInfo != null)
{
return true;
}
if (this.basesHaveEpmInfo == null)
{
this.basesHaveEpmInfo = this.BaseType != null ? this.BaseType.HasEntityPropertyMappings : false;
}
return this.basesHaveEpmInfo.Value;
}
}
///
/// Property used to mark the fact that EpmInfo for the resource type has been initialized
///
internal bool EpmInfoInitialized
{
get;
set;
}
/// The mappings for friendly feeds are V1 compatible or not
internal bool EpmIsV1Compatible
{
get
{
Debug.Assert(this.isReadOnly, "Resource type must already be read-only.");
this.InitializeProperties();
return !this.HasEntityPropertyMappings || this.EpmTargetTree.IsV1Compatible;
}
}
///
/// Tree of source paths for EntityPropertyMappingAttributes on this resource type
///
internal EpmSourceTree EpmSourceTree
{
get
{
if (this.epmInfo == null)
{
this.epmInfo = new EpmInfoPerResourceType();
}
return this.epmInfo.EpmSourceTree;
}
}
///
/// Tree of target paths for EntityPropertyMappingAttributes on this resource type
///
internal EpmTargetTree EpmTargetTree
{
get
{
Debug.Assert(this.epmInfo != null, "Must have valid EpmInfo");
return this.epmInfo.EpmTargetTree;
}
}
/// Inherited EpmInfo
internal IList InheritedEpmInfo
{
get
{
Debug.Assert(this.epmInfo != null, "Must have valid EpmInfo");
return this.epmInfo.InheritedEpmInfo;
}
}
/// Own EpmInfo
internal IList OwnEpmInfo
{
get
{
Debug.Assert(this.epmInfo != null, "Must have valid EpmInfo");
return this.epmInfo.OwnEpmInfo;
}
}
/// All EpmInfo i.e. both own and inherited.
internal IEnumerable AllEpmInfo
{
get
{
Debug.Assert(this.epmInfo != null, "Must have valid EpmInfo");
return this.epmInfo.OwnEpmInfo.Concat(this.epmInfo.InheritedEpmInfo);
}
}
#endregion Properties.
#region Methods.
///
/// Get a ResourceType representing a primitive type given a .NET System.Type object
///
/// .NET type to get the primitive type from
/// A ResourceType object representing the primitive type or null if not primitive
public static ResourceType GetPrimitiveResourceType(Type type)
{
WebUtil.CheckArgumentNull(type, "type");
foreach (ResourceType resourceType in WebUtil.GetPrimitiveTypes())
{
if (resourceType.InstanceType == type)
{
return resourceType;
}
}
return null;
}
///
/// Adds the given property to this ResourceType instance
///
/// resource property to be added
public void AddProperty(ResourceProperty property)
{
WebUtil.CheckArgumentNull(property, "property");
// only check whether the property with the same name exists in this type.
// we will look in base types properties when the type is sealed.
this.ThrowIfSealed();
// add the property to the list of properties declared on this type.
this.AddPropertyInternal(property);
}
///
/// Adds an for the resource type.
///
/// Given
public void AddEntityPropertyMappingAttribute(EntityPropertyMappingAttribute attribute)
{
WebUtil.CheckArgumentNull(attribute, "attribute");
// EntityPropertyMapping attribute can not be added to readonly resource types.
this.ThrowIfSealed();
if (this.ResourceTypeKind != ResourceTypeKind.EntityType)
{
throw new InvalidOperationException(Strings.EpmOnlyAllowedOnEntityTypes(this.Name));
}
if (this.epmInfo == null)
{
this.epmInfo = new EpmInfoPerResourceType();
}
this.OwnEpmInfo.Add(attribute);
}
///
/// Make the resource type readonly from now on. This means that no more changes can be made to the resource type anymore.
///
public void SetReadOnly()
{
#if DEBUG
IList currentPropertyCollection = this.propertiesDeclaredOnThisType;
#endif
// if its already sealed, its a no-op
if (this.isReadOnly)
{
return;
}
// We need to set readonly at the start to avoid any circular loops that may result due to navigation properties.
// If there are any exceptions, we need to set readonly to false.
this.isReadOnly = true;
// There can be properties with the same name in the base class also (using the new construct)
// if the base type is not null, then we need to make sure that there is no property with the same name.
// Otherwise, we are only populating property declared for this type and clr gaurantees that they are unique
if (this.BaseType != null)
{
this.BaseType.SetReadOnly();
// Mark current type as OpenType if base is an OpenType
if (this.BaseType.IsOpenType && this.ResourceTypeKind != ResourceTypeKind.ComplexType)
{
this.isOpenType = true;
}
// Mark the current type as being a Media Link Entry if the base type is a Media Link Entry.
if (this.BaseType.IsMediaLinkEntry)
{
this.isMediaLinkEntry = true;
}
// Make sure current type is not a CLR type if base is not a CLR type.
if (!this.BaseType.CanReflectOnInstanceType)
{
this.canReflectOnInstanceType = false;
}
}
// set all the properties to readonly
if (this.propertiesDeclaredOnThisType != null)
{
foreach (ResourceProperty p in this.propertiesDeclaredOnThisType)
{
p.SetReadOnly();
}
}
#if DEBUG
// We cannot change the properties collection method. Basically, we should not be calling Properties or PropertiesDeclaredOnThisType properties
// since they call the virtual LoadPropertiesDeclaredOnThisType and we want to postpone that virtual call until we actually need to do something
// more useful with the properties
Debug.Assert(Object.ReferenceEquals(this.propertiesDeclaredOnThisType, currentPropertyCollection), "We should not have modified the properties collection instance");
#endif
}
/// By initializing the EpmInfo for the resource type, ensures that the information is available for de-serialization.
internal void EnsureEpmInfoAvailability()
{
this.InitializeProperties();
}
/// Given a resource type, builds the EntityPropertyMappingInfo for each EntityPropertyMappingAttribute on it
/// Resouce type for which EntityPropertyMappingAttribute discovery is happening
internal void BuildReflectionEpmInfo(ResourceType currentResourceType)
{
if (currentResourceType.BaseType != null)
{
this.BuildReflectionEpmInfo(currentResourceType.BaseType);
}
foreach (EntityPropertyMappingAttribute epmAttr in currentResourceType.InstanceType.GetCustomAttributes(typeof(EntityPropertyMappingAttribute), currentResourceType.BaseType != null ? false : true))
{
this.BuildEpmInfo(epmAttr, currentResourceType, false);
if (this == currentResourceType)
{
if (!this.PropertyExistsInCurrentType(epmAttr))
{
this.InheritedEpmInfo.Add(epmAttr);
}
else
{
this.OwnEpmInfo.Add(epmAttr);
}
}
}
}
///
/// Builds the EntityPropertyMappingInfo corresponding to an EntityPropertyMappingAttribute, also builds the delegate to
/// be invoked in order to retrieve the property provided in the
///
/// Source EntityPropertyMappingAttribute
/// Type that has the attribute applied to it
/// Is EF provider being initialized, used for error message formatting
internal void BuildEpmInfo(EntityPropertyMappingAttribute epmAttr, ResourceType definingType, bool isEFProvider)
{
// We don't need to check for null/empty status of the source path because it is already checked
// in the constructor for EntityPropertyMappingAttribute
ParameterExpression rsrcParam = Expression.Parameter(typeof(object), "rsrc");
ParameterExpression providerParam = Expression.Parameter(typeof(DataServiceProviderWrapper), "provider");
ResourceProperty resourceProperty = null;
Expression propValReaderExpr = this.BuildPropertyReader(
rsrcParam,
providerParam,
this,
epmAttr.SourcePath.Split('/'),
0,
ref resourceProperty);
Delegate dlgPropValReader = Expression.Lambda(propValReaderExpr, rsrcParam, providerParam).Compile();
this.EpmSourceTree.Add(new EntityPropertyMappingInfo { Attribute = epmAttr, PropValReader = dlgPropValReader, DefiningType = definingType, IsEFProvider = isEFProvider });
}
///
/// Sets the value on the object
///
/// Target path segment containing the corresponding attribute information
/// Object on which to set property
/// Value to be set
/// Current deserializer
internal void SetEpmValue(EpmTargetPathSegment currentSegment, Object currentValue, object propertyValue, EpmContentDeSerializerBase deserializer)
{
if (currentSegment.EpmInfo.Attribute.KeepInContent == false)
{
this.SetPropertyValueFromPath(
currentSegment.EpmInfo.Attribute.SourcePath.Split('/'),
this,
currentValue,
propertyValue,
0,
deserializer);
}
}
///
/// Given a collection of corresponding to a property access path
/// on the object, sets the on the property
///
/// Property access path where each element is a property name
/// Resource type for which to set the property
/// Object on which to set property
/// Value of property
/// Index of the current segment being looked at
/// Current deserializer
internal void SetPropertyValueFromPath(
String[] segments,
ResourceType resourceType,
object element,
object propertyValue,
int currentIndex,
EpmContentDeSerializerBase deserializer)
{
String currentSegment = segments[currentIndex];
ResourceProperty clientProp = resourceType.TryResolvePropertyName(currentSegment);
ResourceType propertyType;
if (clientProp == null && resourceType.IsOpenType == false)
{
throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidPropertyNameSpecified(currentSegment, resourceType.FullName));
}
// If this is a open property OR we do not have to do type conversion for primitive types,
// read the type from the payload.
if (clientProp == null ||
(!deserializer.Service.Configuration.EnableTypeConversion && clientProp.ResourceType.ResourceTypeKind == ResourceTypeKind.Primitive))
{
String foundTypeName = deserializer.PropertiesApplied.MapPropertyToType(String.Join("/", segments, 0, currentIndex + 1));
if (foundTypeName != null)
{
propertyType = WebUtil.TryResolveResourceType(deserializer.Service.Provider, foundTypeName);
if (propertyType == null)
{
throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidTypeName(foundTypeName));
}
if (propertyType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
throw DataServiceException.CreateBadRequestError(
Strings.PlainXml_EntityTypeNotSupported(propertyType.FullName));
}
}
else
{
propertyType = ResourceType.PrimitiveStringResourceType;
}
}
else
{
propertyType = clientProp.ResourceType;
}
object currentValue;
// Re-construct the source path to add the newly applied property
string sourcePath = string.Join("/", segments, 0, currentIndex + 1);
switch (propertyType.ResourceTypeKind)
{
case ResourceTypeKind.ComplexType:
if (!deserializer.PropertiesApplied.Lookup(sourcePath))
{
// Complex types are treated as atomic and we never allow merging of properties belonging to
// a complex type. In other words, we either update the whole complex type or not at all,
// never just a subset of its properties. If the complex property has not been applied yet
// we create a new instance then apply its property mappings.
currentValue = deserializer.Updatable.CreateResource(null, propertyType.FullName);
ResourceType.SetEpmProperty(element, currentSegment, currentValue, sourcePath, deserializer);
}
else
{
// We've already created a new instance of the complex property by now, reuse the same instance.
currentValue = deserializer.Updatable.GetValue(element, currentSegment);
Debug.Assert(currentValue != null, "currentValue != null -- we should never be here if the complex property were null.");
}
this.SetPropertyValueFromPath(segments, propertyType, currentValue, propertyValue, ++currentIndex, deserializer);
break;
case ResourceTypeKind.EntityType:
throw DataServiceException.CreateBadRequestError(
Strings.PlainXml_NavigationPropertyNotSupported(clientProp.Name));
default:
Debug.Assert(
propertyType.ResourceTypeKind == ResourceTypeKind.Primitive,
"property.TypeKind == ResourceTypeKind.Primitive -- metadata shouldn't return " + propertyType.ResourceTypeKind);
currentValue = PlainXmlDeserializer.ConvertValuesForXml(propertyValue, currentSegment, propertyType.InstanceType);
// Do not try to update the property if it is a key property
if (!deserializer.IsUpdateOperation || clientProp == null || !clientProp.IsOfKind(ResourcePropertyKind.Key))
{
ResourceType.SetEpmProperty(element, currentSegment, currentValue, sourcePath, deserializer);
}
break;
}
}
///
/// Changes the key property to non key property and removes it from the key properties list
///
internal void RemoveKeyProperties()
{
Debug.Assert(!this.isReadOnly, "The resource type cannot be sealed - RemoveKeyProperties");
ReadOnlyCollection key = this.KeyProperties;
Debug.Assert(key.Count == 1, "Key Properties count must be zero");
Debug.Assert(this.BaseType == null, "BaseType must be null");
Debug.Assert(key[0].IsOfKind(ResourcePropertyKind.Key), "must be key property");
ResourceProperty property = key[0];
property.Kind = property.Kind ^ ResourcePropertyKind.Key;
}
/// Tries to find the property for the specified name.
/// Name of property to resolve.
/// Resolved property; possibly null.
internal ResourceProperty TryResolvePropertyName(string propertyName)
{
// In case of empty property name this will return null, which means propery is not found
return this.Properties.FirstOrDefault(p => p.Name == propertyName);
}
/// Tries to find the property declared on this type for the specified name.
/// Name of property to resolve.
/// Resolved property; possibly null.
internal ResourceProperty TryResolvePropertiesDeclaredOnThisTypeByName(string propertyName)
{
// In case of empty property name this will return null, which means propery is not found
return this.PropertiesDeclaredOnThisType.FirstOrDefault(p => p.Name == propertyName);
}
///
/// Checks if the given type is assignable to this type. In other words, if this type
/// is a subtype of the given type or not.
///
/// resource type to check.
/// true, if the given type is assignable to this type. Otherwise returns false.
internal bool IsAssignableFrom(ResourceType superType)
{
while (superType != null)
{
if (superType == this)
{
return true;
}
superType = superType.BaseType;
}
return false;
}
///
/// Gets the property info for the resource property
///
/// Resource property instance to get the property info
/// Returns the propertyinfo object for the specified resource property.
/// The method searchies this type as well as all its base types for the property.
internal PropertyInfo GetPropertyInfo(ResourceProperty resourceProperty)
{
Debug.Assert(resourceProperty != null, "resourceProperty != null");
Debug.Assert(resourceProperty.CanReflectOnInstanceTypeProperty, "resourceProperty.CanReflectOnInstanceTypeProperty");
PropertyInfo propertyInfo = null;
ResourceType resourceType = this;
while (propertyInfo == null && resourceType != null)
{
propertyInfo = resourceType.GetPropertyInfoDecaredOnThisType(resourceProperty);
resourceType = resourceType.BaseType;
}
Debug.Assert(propertyInfo != null, "propertyInfo != null");
return propertyInfo;
}
/// Sets the value of the property.
/// The object whose property needs to be set.
/// new value for the property.
/// metadata for the property to be set.
internal void SetValue(object instance, object propertyValue, ResourceProperty resourceProperty)
{
Debug.Assert(instance != null, "instance != null");
Debug.Assert(resourceProperty != null, "resourceProperty != null");
MethodInfo setMethod = this.GetPropertyInfo(resourceProperty).GetSetMethod();
if (setMethod == null)
{
throw DataServiceException.CreateBadRequestError(Strings.BadRequest_PropertyValueCannotBeSet(resourceProperty.Name));
}
try
{
setMethod.Invoke(instance, new object[] { propertyValue });
}
catch (TargetInvocationException exception)
{
ErrorHandler.HandleTargetInvocationException(exception);
throw;
}
catch (ArgumentException exception)
{
throw DataServiceException.CreateBadRequestError(Strings.BadRequest_ErrorInSettingPropertyValue(resourceProperty.Name), exception);
}
}
///
/// Return the list of properties declared by this resource type. This method gives a chance to lazy load the properties
/// of a resource type, instead of loading them upfront. This property will be called once and only once, whenever
/// ResourceType.Properties or ResourceType.PropertiesDeclaredOnThisType property is accessed.
///
/// the list of properties declared on this type.
protected virtual IEnumerable LoadPropertiesDeclaredOnThisType()
{
return new ResourceProperty[0];
}
///
/// Compares two resource property instances, sorting them so keys are first,
/// and are alphabetically ordered in case-insensitive ordinal order.
///
/// First property to compare.
/// Second property to compare.
///
/// Less than zero if a sorts before b; zero if equal; greater than zero if a sorts
/// after b.
///
private static int ResourcePropertyComparison(ResourceProperty a, ResourceProperty b)
{
return StringComparer.OrdinalIgnoreCase.Compare(a.Name, b.Name);
}
///
/// Sets a mapped property value and mark its source path as applied
///
/// Object on which to set the property
/// Name of the property
/// Value of the property
/// Source mapping path for the property to be set
/// Current deserializer
private static void SetEpmProperty(object element, string propertyName, object propertyValue, string sourcePath, EpmContentDeSerializerBase deserializer)
{
deserializer.Updatable.SetValue(element, propertyName, propertyValue);
deserializer.PropertiesApplied.AddAppliedProperty(sourcePath, false);
}
///
/// Initializes all properties for the resource type, to be used by Properties getter.
///
/// Collection of properties exposed by this resource type.
private ReadOnlyCollection InitializeProperties()
{
if (this.allProperties == null)
{
ReadOnlyCollection readOnlyAllProps;
List allProps = new List();
if (this.BaseType != null)
{
allProps.AddRange(this.BaseType.Properties);
}
allProps.AddRange(this.PropertiesDeclaredOnThisType);
readOnlyAllProps = new ReadOnlyCollection(allProps);
if (!this.isReadOnly)
{
return readOnlyAllProps;
}
this.allProperties = readOnlyAllProps;
}
Debug.Assert(this.isReadOnly, "Propeties - at this point, the resource type must be readonly");
return this.allProperties;
}
///
/// Validate the given and adds it to the list of properties for this type
///
/// property which needs to be added.
private void AddPropertyInternal(ResourceProperty property)
{
if (this.propertiesDeclaredOnThisType == null)
{
this.propertiesDeclaredOnThisType = new List();
}
foreach (ResourceProperty resourceProperty in this.propertiesDeclaredOnThisType)
{
if (resourceProperty.Name == property.Name)
{
throw new InvalidOperationException(Strings.ResourceType_PropertyWithSameNameAlreadyExists(resourceProperty.Name, this.FullName));
}
}
if (property.IsOfKind(ResourcePropertyKind.Key))
{
if (this.baseType != null)
{
throw new InvalidOperationException(Strings.ResourceType_NoKeysInDerivedTypes);
}
if (this.ResourceTypeKind != ResourceTypeKind.EntityType)
{
throw new InvalidOperationException(Strings.ResourceType_KeyPropertiesOnlyOnEntityTypes);
}
Debug.Assert(property.TypeKind == ResourceTypeKind.Primitive, "This check must have been done in ResourceProperty.ValidatePropertyParameters method");
Debug.Assert(!property.IsOfKind(ResourcePropertyKind.ETag), "This check must have been done in ResourceProperty.ValidatePropertyParameters method");
Debug.Assert(property.IsOfKind(ResourcePropertyKind.Primitive), "This check must have been done in ResourceProperty.ValidatePropertyParameters method");
}
if (property.IsOfKind(ResourcePropertyKind.ETag))
{
if (this.ResourceTypeKind != ResourceTypeKind.EntityType)
{
throw new InvalidOperationException(Strings.ResourceType_ETagPropertiesOnlyOnEntityTypes);
}
#if DEBUG
Debug.Assert(property.TypeKind == ResourceTypeKind.Primitive, "This check must have been done in ResourceProperty.ValidatePropertyParameters method");
Debug.Assert(property.IsOfKind(ResourcePropertyKind.Primitive), "This check must have been done in ResourceProperty.ValidatePropertyParameters method");
Debug.Assert(!property.IsOfKind(ResourcePropertyKind.Key), "This check must have been done in ResourceProperty.ValidatePropertyParameters method");
#endif
}
this.propertiesDeclaredOnThisType.Add(property);
}
///
/// Gets the property info for the resource property declared on this type
///
/// Resource property instance to get the property info
/// Returns the propertyinfo object for the specified resource property.
private PropertyInfo GetPropertyInfoDecaredOnThisType(ResourceProperty resourceProperty)
{
Debug.Assert(resourceProperty != null, "resourceProperty != null");
Debug.Assert(resourceProperty.CanReflectOnInstanceTypeProperty, "resourceProperty.CanReflectOnInstanceTypeProperty");
if (this.propertyInfosDeclaredOnThisType == null)
{
this.propertyInfosDeclaredOnThisType = new Dictionary(ReferenceEqualityComparer.Instance);
}
PropertyInfo propertyInfo;
if (!this.propertyInfosDeclaredOnThisType.TryGetValue(resourceProperty, out propertyInfo))
{
BindingFlags bindingFlags = WebUtil.PublicInstanceBindingFlags;
propertyInfo = this.InstanceType.GetProperty(resourceProperty.Name, bindingFlags);
if (propertyInfo == null)
{
throw new DataServiceException(500, Strings.BadProvider_UnableToGetPropertyInfo(this.FullName, resourceProperty.Name));
}
this.propertyInfosDeclaredOnThisType.Add(resourceProperty, propertyInfo);
}
Debug.Assert(propertyInfo != null, "propertyInfo != null");
return propertyInfo;
}
/// Given a resource type, builds the EntityPropertyMappingInfo for each of the dynamic entity property mapping attribute
/// Resouce type for which EntityPropertyMappingAttribute discovery is happening
private void BuildDynamicEpmInfo(ResourceType currentResourceType)
{
if (currentResourceType.BaseType != null)
{
this.BuildDynamicEpmInfo(currentResourceType.BaseType);
}
if (currentResourceType.HasEntityPropertyMappings)
{
foreach (EntityPropertyMappingAttribute epmAttr in currentResourceType.AllEpmInfo.ToList())
{
this.BuildEpmInfo(epmAttr, currentResourceType, false);
if (this == currentResourceType)
{
if (!this.PropertyExistsInCurrentType(epmAttr))
{
this.InheritedEpmInfo.Add(epmAttr);
this.OwnEpmInfo.Remove(epmAttr);
}
else
{
Debug.Assert(this.OwnEpmInfo.SingleOrDefault(attr => Object.ReferenceEquals(epmAttr, attr)) != null, "Own epmInfo should already have the given instance");
}
}
}
}
}
///
/// Given a source property path in segmented form, creates the correponding delegate that reads the
/// property value from the resource type instance
///
/// Generated expression tree
/// Parameter expression for provider wrapper
/// Resource type whose property is to be read
/// Collection of property names used for resource type property lookup
/// Index of current property name in
/// Property representing the final property in the path.
/// Expression tree which results in access to property value
private Expression BuildPropertyReader(Expression expr, Expression providerParam, ResourceType rsrcType, String[] srcPathSegments, int currentSegment, ref ResourceProperty rsrcProp)
{
if (currentSegment == srcPathSegments.Length)
{
return Expression.Convert(expr, WebUtil.GetTypeAllowingNull(expr.Type));
}
String srcPathPart = srcPathSegments[currentSegment];
rsrcProp = rsrcType != null ? rsrcType.TryResolvePropertyName(srcPathPart) : null;
// o.Prop
Expression objectDotProp;
// IProjectedResult.GetProjectedPropertyValue
Expression projectedResultGetProp;
if (rsrcProp != 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 (!rsrcProp.IsOfKind(currentSegment == srcPathSegments.Length - 1 ? ResourcePropertyKind.Primitive : ResourcePropertyKind.ComplexType))
{
throw new InvalidOperationException(Strings.EpmSourceTree_EndsWithNonPrimitiveType(srcPathPart));
}
// object IProjectedResult.GetProjectedPropertyValue(string)
projectedResultGetProp = Expression.Call(
Expression.Convert(expr, typeof(IProjectedResult)),
ResourceType.IProjectedResultGetProjectedPropertyValueMethodInfo,
Expression.Constant(rsrcProp.Name));
Type nullableResourcePropertyType = WebUtil.GetTypeAllowingNull(rsrcProp.Type);
projectedResultGetProp = Expression.Condition(
Expression.Equal(projectedResultGetProp, Expression.Constant(DBNull.Value)),
Expression.Constant(null, nullableResourcePropertyType),
Expression.Convert(projectedResultGetProp, nullableResourcePropertyType));
Debug.Assert(rsrcType != null, "rsrcType != null");
objectDotProp = Expression.Call(
providerParam,
ResourceType.GetPropertyValueMethodInfo,
expr,
Expression.Constant(rsrcProp),
Expression.Constant(rsrcType));
objectDotProp = Expression.Convert(objectDotProp, WebUtil.GetTypeAllowingNull(rsrcProp.Type));
}
else
{
if (rsrcType == null || rsrcType.IsOpenType)
{
// object IProjectedResult.GetProjectedPropertyValue(string)
projectedResultGetProp = Expression.Call(
Expression.Convert(expr, typeof(IProjectedResult)),
ResourceType.IProjectedResultGetProjectedPropertyValueMethodInfo,
Expression.Constant(srcPathPart));
projectedResultGetProp = Expression.Condition(
Expression.Equal(projectedResultGetProp, Expression.Constant(DBNull.Value)),
Expression.Constant(null, typeof(object)),
projectedResultGetProp);
// object WebUtil.GetNamedPropertyValue(object, string, provider)
objectDotProp = Expression.Call(
null, /* instance */
WebUtil.GetNamedPropertyValueMethodInfo,
expr,
Expression.Constant(srcPathPart),
providerParam);
}
else
{
throw new InvalidOperationException(Strings.EpmSourceTree_InaccessiblePropertyOnType(srcPathPart, rsrcType.Name));
}
}
Expression propertyAccess;
// IProjectedResult can occur only on the first level, as we don't allow projections into nav. properties
// or complex types.
// If it so happens that the source is a value type, it can't be IProjectedResult anyway.
if (currentSegment == 0 && !expr.Type.IsValueType)
{
if (!WebUtil.TypeAllowsNull(objectDotProp.Type))
{
objectDotProp = Expression.Convert(objectDotProp, WebUtil.GetTypeAllowingNull(objectDotProp.Type));
}
// if (o is IProjectedResult) then o.GetProjectedPropertyValue(); else o.Prop;
propertyAccess = Expression.Condition(
Expression.TypeIs(expr, typeof(IProjectedResult)),
projectedResultGetProp,
objectDotProp);
}
else
{
propertyAccess = objectDotProp;
}
Expression recursiveExpr = this.BuildPropertyReader(
propertyAccess,
providerParam,
rsrcProp != null ? rsrcProp.ResourceType : null,
srcPathSegments,
++currentSegment,
ref rsrcProp);
// o == null
BinaryExpression objectIsNull = Expression.Equal(expr, Expression.Constant(null));
ConstantExpression nullableNull = Expression.Constant(
null,
WebUtil.GetTypeAllowingNull(recursiveExpr.Type));
// if (o == null) then null; else o.Prop;
return Expression.Condition(objectIsNull, nullableNull, recursiveExpr);
}
///
/// Does given property in the attribute exist in this type or one of it's base types
///
/// Attribute which has PropertyName
/// true if property exists in current type, false otherwise
private bool PropertyExistsInCurrentType(EntityPropertyMappingAttribute epmAttr)
{
int indexOfSeparator = epmAttr.SourcePath.IndexOf('/');
String propertyToLookFor = indexOfSeparator == -1 ? epmAttr.SourcePath : epmAttr.SourcePath.Substring(0, indexOfSeparator);
return this.PropertiesDeclaredOnThisType.Any(p => p.Name == propertyToLookFor);
}
///
/// Checks if the resource type is sealed. If not, it throws an InvalidOperationException.
///
private void ThrowIfSealed()
{
if (this.isReadOnly)
{
throw new InvalidOperationException(Strings.ResourceType_Sealed(this.FullName));
}
}
///
/// Calls the virtual LoadPropertiesDeclaredOnThisType method, if its not already called and then
/// adds the properties returned by the method to the list of properties for this type.
///
private void GetPropertiesDeclaredOnThisType()
{
// We just call the virtual LoadPropertiesDeclaredOnThisType method only once. If it hasn't been called yet,
// then call the method and update the state to reflect that.
if (!this.isLoadPropertiesMethodCalled)
{
foreach (ResourceProperty p in this.LoadPropertiesDeclaredOnThisType())
{
this.AddPropertyInternal(p);
// if this type is already set to readonly, make sure that new properties returned by the virtual method
// are also set to readonly
if (this.IsReadOnly)
{
p.SetReadOnly();
}
}
this.isLoadPropertiesMethodCalled = true;
}
}
///
/// This method is called only when the Properties property is called and the type is already set to read-only.
/// This method validates all the properties w.r.t to the base type and calls SetReadOnly on all the properties.
///
private void ValidateType()
{
Debug.Assert(this.isLoadPropertiesMethodCalled && this.IsReadOnly, "This method must be invoked only if LoadPropertiesDeclaredOnThisType has been called and the type is set to ReadOnly");
if (this.BaseType != null)
{
// make sure that there are no properties with the same name. Properties with duplicate name within the type
// is already checked in AddProperty method
foreach (ResourceProperty rp in this.BaseType.Properties)
{
if (this.propertiesDeclaredOnThisType.Where(p => p.Name == rp.Name).FirstOrDefault() != null)
{
throw new InvalidOperationException(Strings.ResourceType_PropertyWithSameNameAlreadyExists(rp.Name, this.FullName));
}
}
}
else if (this.ResourceTypeKind == ResourceTypeKind.EntityType)
{
if (this.propertiesDeclaredOnThisType.Where(p => p.IsOfKind(ResourcePropertyKind.Key)).FirstOrDefault() == null)
{
throw new InvalidOperationException(Strings.ResourceType_MissingKeyPropertiesForEntity(this.FullName));
}
}
// set all the properties to readonly
foreach (ResourceProperty p in this.propertiesDeclaredOnThisType)
{
p.SetReadOnly();
// Note that we cache the propertyinfo objects for each CLR properties in the ResourceType class
// rather than the ResourceProperty class because the same ResourceProperty instance can be added
// to multiple ResourceType instances.
if (p.CanReflectOnInstanceTypeProperty)
{
this.GetPropertyInfoDecaredOnThisType(p);
}
}
// Resolve EpmInfos now that everything in the type hierarchy is readonly
try
{
if (this.EpmInfoInitialized == false)
{
this.BuildDynamicEpmInfo(this);
this.EpmInfoInitialized = true;
}
}
catch
{
// If an exception was thrown from this.BuildDynamicEpmInfo(this) method
// EpmSourceTree and EpmTargetTree may be only half constructed and need to be reset.
if (this.HasEntityPropertyMappings && !this.EpmInfoInitialized)
{
this.epmInfo.Reset();
}
throw;
}
}
#endregion Methods.
#region EpmInfoPerResourceType
/// Holder of Epm related data structure per resource type
private sealed class EpmInfoPerResourceType
{
/// EpmSourceTree per
private EpmSourceTree epmSourceTree;
/// EpmTargetTree per
private EpmTargetTree epmTargetTree;
/// Inherited EpmInfo
private List inheritedEpmInfo;
/// Own EpmInfo
private List ownEpmInfo;
/// Property for obtaining EpmSourceTree for a type
internal EpmSourceTree EpmSourceTree
{
get
{
if (this.epmSourceTree == null)
{
this.epmSourceTree = new EpmSourceTree(this.EpmTargetTree);
}
return this.epmSourceTree;
}
}
/// Property for obtaining EpmTargetTree for a type
internal EpmTargetTree EpmTargetTree
{
get
{
if (this.epmTargetTree == null)
{
this.epmTargetTree = new EpmTargetTree();
}
return this.epmTargetTree;
}
}
/// Inherited EpmInfo
internal List InheritedEpmInfo
{
get
{
if (this.inheritedEpmInfo == null)
{
this.inheritedEpmInfo = new List();
}
return this.inheritedEpmInfo;
}
}
/// Own EpmInfo
internal List OwnEpmInfo
{
get
{
if (this.ownEpmInfo == null)
{
this.ownEpmInfo = new List();
}
return this.ownEpmInfo;
}
}
///
/// Removes all data created internally by ResourceType. This is needed when building epm
/// info fails since the trees may be left in undefined state (i.e. half constructed) and
/// if inherited EPM attributes exist duplicates will be added.
///
internal void Reset()
{
this.epmTargetTree = null;
this.epmSourceTree = null;
this.inheritedEpmInfo = null;
}
}
#endregion
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Contains information about a particular resource.
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services.Providers
{
#region Namespaces.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Services.Common;
using System.Data.Services.Parsing;
using System.Data.Services.Serializers;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
#endregion Namespaces.
/// Use this class to represent a DataService type (primitive, complex or entity).
[DebuggerDisplay("{Name}: {InstanceType}, {ResourceTypeKind}")]
public class ResourceType
{
#region Fields.
/// empty list of properties
internal static readonly ReadOnlyCollection EmptyProperties = new ReadOnlyCollection(new ResourceProperty[0]);
/// Primitive string resource type.
internal static readonly ResourceType PrimitiveStringResourceType = ResourceType.GetPrimitiveResourceType(typeof(string));
/// MethodInfo for object DataServiceProviderWrapper.GetPropertyValue(object target, ResourceProperty resourceProperty, ResourceType resourceType).
private static readonly MethodInfo GetPropertyValueMethodInfo = typeof(DataServiceProviderWrapper).GetMethod(
"GetPropertyValue",
WebUtil.PublicInstanceBindingFlags);
/// MethodInfo for object IProjectedResult.GetProjectedPropertyValue(this IProjectedResult value, string propertyName).
private static readonly MethodInfo IProjectedResultGetProjectedPropertyValueMethodInfo = typeof(IProjectedResult).GetMethod(
"GetProjectedPropertyValue",
WebUtil.PublicInstanceBindingFlags);
/// ResourceTypeKind for the type that this structure represents
private readonly ResourceTypeKind resourceTypeKind;
/// Reference to clr type that this resource represents
private readonly Type type;
/// Reference to base resource type
private readonly ResourceType baseType;
/// name of the resource.
private readonly string name;
/// full name of the resource.
private readonly string fullName;
/// Namespace for this type.
private readonly string namespaceName;
/// Whether this type is abstract.
private readonly bool abstractType;
/// Whether the resource type has open properties.
private bool isOpenType;
/// Whether the corresponding instance type actually represents this node's CLR type.
private bool canReflectOnInstanceType;
/// Cached delegate to create a new instance of this type.
private Func constructorDelegate;
/// Cached delegate to serialize parts of this resource into a dictionary.
private Action dictionarySerializerDelegate;
/// List of properties declared in this type (includes properties only defined in this type, not in the base type)
private IList propertiesDeclaredOnThisType;
/// List of all properties for this type (includes properties defined in the base type also)
private ReadOnlyCollection allProperties;
/// list of key properties for this type
private ReadOnlyCollection keyProperties;
/// list of etag properties for this type.
private ReadOnlyCollection etagProperties;
/// If ResourceProperty.CanReflectOnInstanceTypeProperty is true, we cache the PropertyInfo object.
private Dictionary propertyInfosDeclaredOnThisType = new Dictionary(ReferenceEqualityComparer.Instance);
/// EpmInfo for this
private EpmInfoPerResourceType epmInfo;
/// Indicates whether one of the base class of this resource type has EpmInfo.
private bool? basesHaveEpmInfo;
/// is true, if the type is set to readonly.
private bool isReadOnly;
/// True if the resource type includes a default stream
private bool isMediaLinkEntry;
/// True if the virtual load properties is already called, otherwise false.
private bool isLoadPropertiesMethodCalled;
#endregion Fields.
#region Constructors.
///
/// Constructs a new instance of Astoria type using the specified clr type
///
/// clr type that represents the flow format inside the Astoria runtime
/// kind of the resource type
/// base type of the resource type
/// Namespace name of the given resource type.
/// name of the given resource type.
/// whether the resource type is an abstract type or not.
public ResourceType(
Type instanceType,
ResourceTypeKind resourceTypeKind,
ResourceType baseType,
string namespaceName,
string name,
bool isAbstract)
: this(instanceType, baseType, namespaceName, name, isAbstract)
{
WebUtil.CheckArgumentNull(instanceType, "instanceType");
WebUtil.CheckStringArgumentNull(name, "name");
WebUtil.CheckResourceTypeKind(resourceTypeKind, "resourceTypeKind");
if (resourceTypeKind == ResourceTypeKind.Primitive)
{
throw new ArgumentException(Strings.ResourceType_InvalidValueForResourceTypeKind, "resourceTypeKind");
}
if (instanceType.IsValueType)
{
throw new ArgumentException(Strings.ResourceType_TypeCannotBeValueType, "instanceType");
}
this.resourceTypeKind = resourceTypeKind;
}
///
/// Constructs a new instance of Resource type for the given clr primitive type. This constructor must be called only for primitive types.
///
/// clr type representing the primitive type.
/// namespace of the primitive type.
/// name of the primitive type.
internal ResourceType(Type type, string namespaceName, string name)
: this(type, null, namespaceName, name, false)
{
Debug.Assert(WebUtil.IsPrimitiveType(type), "This constructor should be called only for primitive types");
this.resourceTypeKind = ResourceTypeKind.Primitive;
this.isReadOnly = true;
}
///
/// Constructs a new instance of Astoria type using the specified clr type
///
/// clr type from which metadata needs to be pulled
/// base type of the resource type
/// Namespace name of the given resource type.
/// name of the given resource type.
/// whether the resource type is an abstract type or not.
private ResourceType(
Type type,
ResourceType baseType,
string namespaceName,
string name,
bool isAbstract)
{
WebUtil.CheckArgumentNull(type, "type");
WebUtil.CheckArgumentNull(name, "name");
this.name = name;
this.namespaceName = namespaceName ?? string.Empty;
// This is to optimize the string property name in PlainXmlSerializer.WriteStartElementWithType function.
// Checking here is a fixed overhead, and the gain is every time we serialize a string property.
if (name == "String" && Object.ReferenceEquals(namespaceName, XmlConstants.EdmNamespace))
{
this.fullName = XmlConstants.EdmStringTypeName;
}
else
{
this.fullName = string.IsNullOrEmpty(namespaceName) ? name : namespaceName + "." + name;
}
this.type = type;
this.abstractType = isAbstract;
this.canReflectOnInstanceType = true;
if (baseType != null)
{
this.baseType = baseType;
}
}
#endregion Constructors.
#region Properties.
/// True if the resource type includes a default stream
public bool IsMediaLinkEntry
{
[DebuggerStepThrough]
get
{
return this.isMediaLinkEntry;
}
set
{
this.ThrowIfSealed();
if (this.resourceTypeKind != ResourceTypeKind.EntityType && value == true)
{
throw new InvalidOperationException(Strings.ReflectionProvider_HasStreamAttributeOnlyAppliesToEntityType(this.name));
}
this.isMediaLinkEntry = value;
}
}
/// Reference to clr type that this resource represents
public Type InstanceType
{
[DebuggerStepThrough]
get { return this.type; }
}
/// Reference to base resource type, if any
public ResourceType BaseType
{
[DebuggerStepThrough]
get { return this.baseType; }
}
/// ResourceTypeKind of this type
public ResourceTypeKind ResourceTypeKind
{
[DebuggerStepThrough]
get { return this.resourceTypeKind; }
}
/// Returns the list of properties for this type
public ReadOnlyCollection Properties
{
get
{
return this.InitializeProperties();
}
}
/// list of properties declared on this type
public ReadOnlyCollection PropertiesDeclaredOnThisType
{
get
{
ReadOnlyCollection readOnlyProperties = this.propertiesDeclaredOnThisType as ReadOnlyCollection;
if (readOnlyProperties == null)
{
// This method will call the virtual method, if that's not been called yet and add the list of properties
// returned by the virtual method to the properties collection.
this.GetPropertiesDeclaredOnThisType();
readOnlyProperties = new ReadOnlyCollection(this.propertiesDeclaredOnThisType ?? ResourceType.EmptyProperties);
if (!this.isReadOnly)
{
return readOnlyProperties;
}
// First try and validate the type. If that succeeds, then cache the results. otherwise we need to revert the results.
IList propertyCollection = this.propertiesDeclaredOnThisType;
this.propertiesDeclaredOnThisType = readOnlyProperties;
try
{
this.ValidateType();
}
catch (Exception)
{
this.propertiesDeclaredOnThisType = propertyCollection;
throw;
}
}
Debug.Assert(this.isReadOnly, "PropetiesDeclaredInThisType - at this point, the resource type must be readonly");
return readOnlyProperties;
}
}
/// Returns the list of key properties for this type, if this type is entity type.
public ReadOnlyCollection KeyProperties
{
get
{
if (this.keyProperties == null)
{
ResourceType rootType = this;
while (rootType.BaseType != null)
{
rootType = rootType.BaseType;
}
ReadOnlyCollection readOnlyKeyProperties;
if (rootType.Properties == null)
{
readOnlyKeyProperties = ResourceType.EmptyProperties;
}
else
{
List key = rootType.Properties.Where(p => p.IsOfKind(ResourcePropertyKind.Key)).ToList();
key.Sort(ResourceType.ResourcePropertyComparison);
readOnlyKeyProperties = new ReadOnlyCollection(key);
}
if (!this.isReadOnly)
{
return readOnlyKeyProperties;
}
this.keyProperties = readOnlyKeyProperties;
}
Debug.Assert(this.isReadOnly, "KeyProperties - at this point, the resource type must be readonly");
Debug.Assert(
(this.ResourceTypeKind != ResourceTypeKind.EntityType && this.keyProperties.Count == 0) ||
(this.ResourceTypeKind == ResourceTypeKind.EntityType && this.keyProperties.Count > 0),
"Entity type must have key properties and non-entity types cannot have key properties");
return this.keyProperties;
}
}
/// Returns the list of etag properties for this type.
public ReadOnlyCollection ETagProperties
{
get
{
if (this.etagProperties == null)
{
ReadOnlyCollection etag = new ReadOnlyCollection(this.Properties.Where(p => p.IsOfKind(ResourcePropertyKind.ETag)).ToList());
if (!this.isReadOnly)
{
return etag;
}
this.etagProperties = etag;
}
Debug.Assert(this.isReadOnly, "ETagProperties - at this point, the resource type must be readonly");
return this.etagProperties;
}
}
/// Gets the name of the resource.
public string Name
{
get { return this.name; }
}
/// Gets the fullname of the resource.
public string FullName
{
get { return this.fullName; }
}
/// Returns the namespace of this type.
public string Namespace
{
get { return this.namespaceName; }
}
/// Indicates whether this is an abstract type.
public bool IsAbstract
{
get { return this.abstractType; }
}
/// Indicates whether the resource type has open properties.
public bool IsOpenType
{
[DebuggerStepThrough]
get
{
return this.isOpenType;
}
set
{
this.ThrowIfSealed();
// Complex types can not be marked as open.
if (this.resourceTypeKind == ResourceTypeKind.ComplexType && value == true)
{
throw new InvalidOperationException(Strings.ResourceType_ComplexTypeCannotBeOpen(this.FullName));
}
this.isOpenType = value;
}
}
/// Whether the corresponding instance type actually represents this node's CLR type.
public bool CanReflectOnInstanceType
{
[DebuggerStepThrough]
get
{
return this.canReflectOnInstanceType;
}
set
{
this.ThrowIfSealed();
this.canReflectOnInstanceType = value;
}
}
///
/// PlaceHolder to hold custom state information about resource type.
///
public object CustomState
{
get;
set;
}
///
/// Returns true, if this resource type has been set to read only. Otherwise returns false.
///
public bool IsReadOnly
{
get { return this.isReadOnly; }
}
/// Cached delegate to create a new instance of this type.
internal Func ConstructorDelegate
{
get
{
if (this.constructorDelegate == null)
{
this.constructorDelegate = (Func)
WebUtil.CreateNewInstanceConstructor(this.InstanceType, this.FullName, typeof(object));
}
return this.constructorDelegate;
}
}
/// Cached delegate to serialize parts of this resource into a dictionary.
internal Action DictionarySerializerDelegate
{
get { return this.dictionarySerializerDelegate; }
set { this.dictionarySerializerDelegate = value; }
}
///
/// Do we have entity property mappings for this
///
internal bool HasEntityPropertyMappings
{
get
{
Debug.Assert(this.IsReadOnly, "Type must be read-only.");
if (this.epmInfo != null)
{
return true;
}
if (this.basesHaveEpmInfo == null)
{
this.basesHaveEpmInfo = this.BaseType != null ? this.BaseType.HasEntityPropertyMappings : false;
}
return this.basesHaveEpmInfo.Value;
}
}
///
/// Property used to mark the fact that EpmInfo for the resource type has been initialized
///
internal bool EpmInfoInitialized
{
get;
set;
}
/// The mappings for friendly feeds are V1 compatible or not
internal bool EpmIsV1Compatible
{
get
{
Debug.Assert(this.isReadOnly, "Resource type must already be read-only.");
this.InitializeProperties();
return !this.HasEntityPropertyMappings || this.EpmTargetTree.IsV1Compatible;
}
}
///
/// Tree of source paths for EntityPropertyMappingAttributes on this resource type
///
internal EpmSourceTree EpmSourceTree
{
get
{
if (this.epmInfo == null)
{
this.epmInfo = new EpmInfoPerResourceType();
}
return this.epmInfo.EpmSourceTree;
}
}
///
/// Tree of target paths for EntityPropertyMappingAttributes on this resource type
///
internal EpmTargetTree EpmTargetTree
{
get
{
Debug.Assert(this.epmInfo != null, "Must have valid EpmInfo");
return this.epmInfo.EpmTargetTree;
}
}
/// Inherited EpmInfo
internal IList InheritedEpmInfo
{
get
{
Debug.Assert(this.epmInfo != null, "Must have valid EpmInfo");
return this.epmInfo.InheritedEpmInfo;
}
}
/// Own EpmInfo
internal IList OwnEpmInfo
{
get
{
Debug.Assert(this.epmInfo != null, "Must have valid EpmInfo");
return this.epmInfo.OwnEpmInfo;
}
}
/// All EpmInfo i.e. both own and inherited.
internal IEnumerable AllEpmInfo
{
get
{
Debug.Assert(this.epmInfo != null, "Must have valid EpmInfo");
return this.epmInfo.OwnEpmInfo.Concat(this.epmInfo.InheritedEpmInfo);
}
}
#endregion Properties.
#region Methods.
///
/// Get a ResourceType representing a primitive type given a .NET System.Type object
///
/// .NET type to get the primitive type from
/// A ResourceType object representing the primitive type or null if not primitive
public static ResourceType GetPrimitiveResourceType(Type type)
{
WebUtil.CheckArgumentNull(type, "type");
foreach (ResourceType resourceType in WebUtil.GetPrimitiveTypes())
{
if (resourceType.InstanceType == type)
{
return resourceType;
}
}
return null;
}
///
/// Adds the given property to this ResourceType instance
///
/// resource property to be added
public void AddProperty(ResourceProperty property)
{
WebUtil.CheckArgumentNull(property, "property");
// only check whether the property with the same name exists in this type.
// we will look in base types properties when the type is sealed.
this.ThrowIfSealed();
// add the property to the list of properties declared on this type.
this.AddPropertyInternal(property);
}
///
/// Adds an for the resource type.
///
/// Given
public void AddEntityPropertyMappingAttribute(EntityPropertyMappingAttribute attribute)
{
WebUtil.CheckArgumentNull(attribute, "attribute");
// EntityPropertyMapping attribute can not be added to readonly resource types.
this.ThrowIfSealed();
if (this.ResourceTypeKind != ResourceTypeKind.EntityType)
{
throw new InvalidOperationException(Strings.EpmOnlyAllowedOnEntityTypes(this.Name));
}
if (this.epmInfo == null)
{
this.epmInfo = new EpmInfoPerResourceType();
}
this.OwnEpmInfo.Add(attribute);
}
///
/// Make the resource type readonly from now on. This means that no more changes can be made to the resource type anymore.
///
public void SetReadOnly()
{
#if DEBUG
IList currentPropertyCollection = this.propertiesDeclaredOnThisType;
#endif
// if its already sealed, its a no-op
if (this.isReadOnly)
{
return;
}
// We need to set readonly at the start to avoid any circular loops that may result due to navigation properties.
// If there are any exceptions, we need to set readonly to false.
this.isReadOnly = true;
// There can be properties with the same name in the base class also (using the new construct)
// if the base type is not null, then we need to make sure that there is no property with the same name.
// Otherwise, we are only populating property declared for this type and clr gaurantees that they are unique
if (this.BaseType != null)
{
this.BaseType.SetReadOnly();
// Mark current type as OpenType if base is an OpenType
if (this.BaseType.IsOpenType && this.ResourceTypeKind != ResourceTypeKind.ComplexType)
{
this.isOpenType = true;
}
// Mark the current type as being a Media Link Entry if the base type is a Media Link Entry.
if (this.BaseType.IsMediaLinkEntry)
{
this.isMediaLinkEntry = true;
}
// Make sure current type is not a CLR type if base is not a CLR type.
if (!this.BaseType.CanReflectOnInstanceType)
{
this.canReflectOnInstanceType = false;
}
}
// set all the properties to readonly
if (this.propertiesDeclaredOnThisType != null)
{
foreach (ResourceProperty p in this.propertiesDeclaredOnThisType)
{
p.SetReadOnly();
}
}
#if DEBUG
// We cannot change the properties collection method. Basically, we should not be calling Properties or PropertiesDeclaredOnThisType properties
// since they call the virtual LoadPropertiesDeclaredOnThisType and we want to postpone that virtual call until we actually need to do something
// more useful with the properties
Debug.Assert(Object.ReferenceEquals(this.propertiesDeclaredOnThisType, currentPropertyCollection), "We should not have modified the properties collection instance");
#endif
}
/// By initializing the EpmInfo for the resource type, ensures that the information is available for de-serialization.
internal void EnsureEpmInfoAvailability()
{
this.InitializeProperties();
}
/// Given a resource type, builds the EntityPropertyMappingInfo for each EntityPropertyMappingAttribute on it
/// Resouce type for which EntityPropertyMappingAttribute discovery is happening
internal void BuildReflectionEpmInfo(ResourceType currentResourceType)
{
if (currentResourceType.BaseType != null)
{
this.BuildReflectionEpmInfo(currentResourceType.BaseType);
}
foreach (EntityPropertyMappingAttribute epmAttr in currentResourceType.InstanceType.GetCustomAttributes(typeof(EntityPropertyMappingAttribute), currentResourceType.BaseType != null ? false : true))
{
this.BuildEpmInfo(epmAttr, currentResourceType, false);
if (this == currentResourceType)
{
if (!this.PropertyExistsInCurrentType(epmAttr))
{
this.InheritedEpmInfo.Add(epmAttr);
}
else
{
this.OwnEpmInfo.Add(epmAttr);
}
}
}
}
///
/// Builds the EntityPropertyMappingInfo corresponding to an EntityPropertyMappingAttribute, also builds the delegate to
/// be invoked in order to retrieve the property provided in the
///
/// Source EntityPropertyMappingAttribute
/// Type that has the attribute applied to it
/// Is EF provider being initialized, used for error message formatting
internal void BuildEpmInfo(EntityPropertyMappingAttribute epmAttr, ResourceType definingType, bool isEFProvider)
{
// We don't need to check for null/empty status of the source path because it is already checked
// in the constructor for EntityPropertyMappingAttribute
ParameterExpression rsrcParam = Expression.Parameter(typeof(object), "rsrc");
ParameterExpression providerParam = Expression.Parameter(typeof(DataServiceProviderWrapper), "provider");
ResourceProperty resourceProperty = null;
Expression propValReaderExpr = this.BuildPropertyReader(
rsrcParam,
providerParam,
this,
epmAttr.SourcePath.Split('/'),
0,
ref resourceProperty);
Delegate dlgPropValReader = Expression.Lambda(propValReaderExpr, rsrcParam, providerParam).Compile();
this.EpmSourceTree.Add(new EntityPropertyMappingInfo { Attribute = epmAttr, PropValReader = dlgPropValReader, DefiningType = definingType, IsEFProvider = isEFProvider });
}
///
/// Sets the value on the object
///
/// Target path segment containing the corresponding attribute information
/// Object on which to set property
/// Value to be set
/// Current deserializer
internal void SetEpmValue(EpmTargetPathSegment currentSegment, Object currentValue, object propertyValue, EpmContentDeSerializerBase deserializer)
{
if (currentSegment.EpmInfo.Attribute.KeepInContent == false)
{
this.SetPropertyValueFromPath(
currentSegment.EpmInfo.Attribute.SourcePath.Split('/'),
this,
currentValue,
propertyValue,
0,
deserializer);
}
}
///
/// Given a collection of corresponding to a property access path
/// on the object, sets the on the property
///
/// Property access path where each element is a property name
/// Resource type for which to set the property
/// Object on which to set property
/// Value of property
/// Index of the current segment being looked at
/// Current deserializer
internal void SetPropertyValueFromPath(
String[] segments,
ResourceType resourceType,
object element,
object propertyValue,
int currentIndex,
EpmContentDeSerializerBase deserializer)
{
String currentSegment = segments[currentIndex];
ResourceProperty clientProp = resourceType.TryResolvePropertyName(currentSegment);
ResourceType propertyType;
if (clientProp == null && resourceType.IsOpenType == false)
{
throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidPropertyNameSpecified(currentSegment, resourceType.FullName));
}
// If this is a open property OR we do not have to do type conversion for primitive types,
// read the type from the payload.
if (clientProp == null ||
(!deserializer.Service.Configuration.EnableTypeConversion && clientProp.ResourceType.ResourceTypeKind == ResourceTypeKind.Primitive))
{
String foundTypeName = deserializer.PropertiesApplied.MapPropertyToType(String.Join("/", segments, 0, currentIndex + 1));
if (foundTypeName != null)
{
propertyType = WebUtil.TryResolveResourceType(deserializer.Service.Provider, foundTypeName);
if (propertyType == null)
{
throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidTypeName(foundTypeName));
}
if (propertyType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
throw DataServiceException.CreateBadRequestError(
Strings.PlainXml_EntityTypeNotSupported(propertyType.FullName));
}
}
else
{
propertyType = ResourceType.PrimitiveStringResourceType;
}
}
else
{
propertyType = clientProp.ResourceType;
}
object currentValue;
// Re-construct the source path to add the newly applied property
string sourcePath = string.Join("/", segments, 0, currentIndex + 1);
switch (propertyType.ResourceTypeKind)
{
case ResourceTypeKind.ComplexType:
if (!deserializer.PropertiesApplied.Lookup(sourcePath))
{
// Complex types are treated as atomic and we never allow merging of properties belonging to
// a complex type. In other words, we either update the whole complex type or not at all,
// never just a subset of its properties. If the complex property has not been applied yet
// we create a new instance then apply its property mappings.
currentValue = deserializer.Updatable.CreateResource(null, propertyType.FullName);
ResourceType.SetEpmProperty(element, currentSegment, currentValue, sourcePath, deserializer);
}
else
{
// We've already created a new instance of the complex property by now, reuse the same instance.
currentValue = deserializer.Updatable.GetValue(element, currentSegment);
Debug.Assert(currentValue != null, "currentValue != null -- we should never be here if the complex property were null.");
}
this.SetPropertyValueFromPath(segments, propertyType, currentValue, propertyValue, ++currentIndex, deserializer);
break;
case ResourceTypeKind.EntityType:
throw DataServiceException.CreateBadRequestError(
Strings.PlainXml_NavigationPropertyNotSupported(clientProp.Name));
default:
Debug.Assert(
propertyType.ResourceTypeKind == ResourceTypeKind.Primitive,
"property.TypeKind == ResourceTypeKind.Primitive -- metadata shouldn't return " + propertyType.ResourceTypeKind);
currentValue = PlainXmlDeserializer.ConvertValuesForXml(propertyValue, currentSegment, propertyType.InstanceType);
// Do not try to update the property if it is a key property
if (!deserializer.IsUpdateOperation || clientProp == null || !clientProp.IsOfKind(ResourcePropertyKind.Key))
{
ResourceType.SetEpmProperty(element, currentSegment, currentValue, sourcePath, deserializer);
}
break;
}
}
///
/// Changes the key property to non key property and removes it from the key properties list
///
internal void RemoveKeyProperties()
{
Debug.Assert(!this.isReadOnly, "The resource type cannot be sealed - RemoveKeyProperties");
ReadOnlyCollection key = this.KeyProperties;
Debug.Assert(key.Count == 1, "Key Properties count must be zero");
Debug.Assert(this.BaseType == null, "BaseType must be null");
Debug.Assert(key[0].IsOfKind(ResourcePropertyKind.Key), "must be key property");
ResourceProperty property = key[0];
property.Kind = property.Kind ^ ResourcePropertyKind.Key;
}
/// Tries to find the property for the specified name.
/// Name of property to resolve.
/// Resolved property; possibly null.
internal ResourceProperty TryResolvePropertyName(string propertyName)
{
// In case of empty property name this will return null, which means propery is not found
return this.Properties.FirstOrDefault(p => p.Name == propertyName);
}
/// Tries to find the property declared on this type for the specified name.
/// Name of property to resolve.
/// Resolved property; possibly null.
internal ResourceProperty TryResolvePropertiesDeclaredOnThisTypeByName(string propertyName)
{
// In case of empty property name this will return null, which means propery is not found
return this.PropertiesDeclaredOnThisType.FirstOrDefault(p => p.Name == propertyName);
}
///
/// Checks if the given type is assignable to this type. In other words, if this type
/// is a subtype of the given type or not.
///
/// resource type to check.
/// true, if the given type is assignable to this type. Otherwise returns false.
internal bool IsAssignableFrom(ResourceType superType)
{
while (superType != null)
{
if (superType == this)
{
return true;
}
superType = superType.BaseType;
}
return false;
}
///
/// Gets the property info for the resource property
///
/// Resource property instance to get the property info
/// Returns the propertyinfo object for the specified resource property.
/// The method searchies this type as well as all its base types for the property.
internal PropertyInfo GetPropertyInfo(ResourceProperty resourceProperty)
{
Debug.Assert(resourceProperty != null, "resourceProperty != null");
Debug.Assert(resourceProperty.CanReflectOnInstanceTypeProperty, "resourceProperty.CanReflectOnInstanceTypeProperty");
PropertyInfo propertyInfo = null;
ResourceType resourceType = this;
while (propertyInfo == null && resourceType != null)
{
propertyInfo = resourceType.GetPropertyInfoDecaredOnThisType(resourceProperty);
resourceType = resourceType.BaseType;
}
Debug.Assert(propertyInfo != null, "propertyInfo != null");
return propertyInfo;
}
/// Sets the value of the property.
/// The object whose property needs to be set.
/// new value for the property.
/// metadata for the property to be set.
internal void SetValue(object instance, object propertyValue, ResourceProperty resourceProperty)
{
Debug.Assert(instance != null, "instance != null");
Debug.Assert(resourceProperty != null, "resourceProperty != null");
MethodInfo setMethod = this.GetPropertyInfo(resourceProperty).GetSetMethod();
if (setMethod == null)
{
throw DataServiceException.CreateBadRequestError(Strings.BadRequest_PropertyValueCannotBeSet(resourceProperty.Name));
}
try
{
setMethod.Invoke(instance, new object[] { propertyValue });
}
catch (TargetInvocationException exception)
{
ErrorHandler.HandleTargetInvocationException(exception);
throw;
}
catch (ArgumentException exception)
{
throw DataServiceException.CreateBadRequestError(Strings.BadRequest_ErrorInSettingPropertyValue(resourceProperty.Name), exception);
}
}
///
/// Return the list of properties declared by this resource type. This method gives a chance to lazy load the properties
/// of a resource type, instead of loading them upfront. This property will be called once and only once, whenever
/// ResourceType.Properties or ResourceType.PropertiesDeclaredOnThisType property is accessed.
///
/// the list of properties declared on this type.
protected virtual IEnumerable LoadPropertiesDeclaredOnThisType()
{
return new ResourceProperty[0];
}
///
/// Compares two resource property instances, sorting them so keys are first,
/// and are alphabetically ordered in case-insensitive ordinal order.
///
/// First property to compare.
/// Second property to compare.
///
/// Less than zero if a sorts before b; zero if equal; greater than zero if a sorts
/// after b.
///
private static int ResourcePropertyComparison(ResourceProperty a, ResourceProperty b)
{
return StringComparer.OrdinalIgnoreCase.Compare(a.Name, b.Name);
}
///
/// Sets a mapped property value and mark its source path as applied
///
/// Object on which to set the property
/// Name of the property
/// Value of the property
/// Source mapping path for the property to be set
/// Current deserializer
private static void SetEpmProperty(object element, string propertyName, object propertyValue, string sourcePath, EpmContentDeSerializerBase deserializer)
{
deserializer.Updatable.SetValue(element, propertyName, propertyValue);
deserializer.PropertiesApplied.AddAppliedProperty(sourcePath, false);
}
///
/// Initializes all properties for the resource type, to be used by Properties getter.
///
/// Collection of properties exposed by this resource type.
private ReadOnlyCollection InitializeProperties()
{
if (this.allProperties == null)
{
ReadOnlyCollection readOnlyAllProps;
List allProps = new List();
if (this.BaseType != null)
{
allProps.AddRange(this.BaseType.Properties);
}
allProps.AddRange(this.PropertiesDeclaredOnThisType);
readOnlyAllProps = new ReadOnlyCollection(allProps);
if (!this.isReadOnly)
{
return readOnlyAllProps;
}
this.allProperties = readOnlyAllProps;
}
Debug.Assert(this.isReadOnly, "Propeties - at this point, the resource type must be readonly");
return this.allProperties;
}
///
/// Validate the given and adds it to the list of properties for this type
///
/// property which needs to be added.
private void AddPropertyInternal(ResourceProperty property)
{
if (this.propertiesDeclaredOnThisType == null)
{
this.propertiesDeclaredOnThisType = new List();
}
foreach (ResourceProperty resourceProperty in this.propertiesDeclaredOnThisType)
{
if (resourceProperty.Name == property.Name)
{
throw new InvalidOperationException(Strings.ResourceType_PropertyWithSameNameAlreadyExists(resourceProperty.Name, this.FullName));
}
}
if (property.IsOfKind(ResourcePropertyKind.Key))
{
if (this.baseType != null)
{
throw new InvalidOperationException(Strings.ResourceType_NoKeysInDerivedTypes);
}
if (this.ResourceTypeKind != ResourceTypeKind.EntityType)
{
throw new InvalidOperationException(Strings.ResourceType_KeyPropertiesOnlyOnEntityTypes);
}
Debug.Assert(property.TypeKind == ResourceTypeKind.Primitive, "This check must have been done in ResourceProperty.ValidatePropertyParameters method");
Debug.Assert(!property.IsOfKind(ResourcePropertyKind.ETag), "This check must have been done in ResourceProperty.ValidatePropertyParameters method");
Debug.Assert(property.IsOfKind(ResourcePropertyKind.Primitive), "This check must have been done in ResourceProperty.ValidatePropertyParameters method");
}
if (property.IsOfKind(ResourcePropertyKind.ETag))
{
if (this.ResourceTypeKind != ResourceTypeKind.EntityType)
{
throw new InvalidOperationException(Strings.ResourceType_ETagPropertiesOnlyOnEntityTypes);
}
#if DEBUG
Debug.Assert(property.TypeKind == ResourceTypeKind.Primitive, "This check must have been done in ResourceProperty.ValidatePropertyParameters method");
Debug.Assert(property.IsOfKind(ResourcePropertyKind.Primitive), "This check must have been done in ResourceProperty.ValidatePropertyParameters method");
Debug.Assert(!property.IsOfKind(ResourcePropertyKind.Key), "This check must have been done in ResourceProperty.ValidatePropertyParameters method");
#endif
}
this.propertiesDeclaredOnThisType.Add(property);
}
///
/// Gets the property info for the resource property declared on this type
///
/// Resource property instance to get the property info
/// Returns the propertyinfo object for the specified resource property.
private PropertyInfo GetPropertyInfoDecaredOnThisType(ResourceProperty resourceProperty)
{
Debug.Assert(resourceProperty != null, "resourceProperty != null");
Debug.Assert(resourceProperty.CanReflectOnInstanceTypeProperty, "resourceProperty.CanReflectOnInstanceTypeProperty");
if (this.propertyInfosDeclaredOnThisType == null)
{
this.propertyInfosDeclaredOnThisType = new Dictionary(ReferenceEqualityComparer.Instance);
}
PropertyInfo propertyInfo;
if (!this.propertyInfosDeclaredOnThisType.TryGetValue(resourceProperty, out propertyInfo))
{
BindingFlags bindingFlags = WebUtil.PublicInstanceBindingFlags;
propertyInfo = this.InstanceType.GetProperty(resourceProperty.Name, bindingFlags);
if (propertyInfo == null)
{
throw new DataServiceException(500, Strings.BadProvider_UnableToGetPropertyInfo(this.FullName, resourceProperty.Name));
}
this.propertyInfosDeclaredOnThisType.Add(resourceProperty, propertyInfo);
}
Debug.Assert(propertyInfo != null, "propertyInfo != null");
return propertyInfo;
}
/// Given a resource type, builds the EntityPropertyMappingInfo for each of the dynamic entity property mapping attribute
/// Resouce type for which EntityPropertyMappingAttribute discovery is happening
private void BuildDynamicEpmInfo(ResourceType currentResourceType)
{
if (currentResourceType.BaseType != null)
{
this.BuildDynamicEpmInfo(currentResourceType.BaseType);
}
if (currentResourceType.HasEntityPropertyMappings)
{
foreach (EntityPropertyMappingAttribute epmAttr in currentResourceType.AllEpmInfo.ToList())
{
this.BuildEpmInfo(epmAttr, currentResourceType, false);
if (this == currentResourceType)
{
if (!this.PropertyExistsInCurrentType(epmAttr))
{
this.InheritedEpmInfo.Add(epmAttr);
this.OwnEpmInfo.Remove(epmAttr);
}
else
{
Debug.Assert(this.OwnEpmInfo.SingleOrDefault(attr => Object.ReferenceEquals(epmAttr, attr)) != null, "Own epmInfo should already have the given instance");
}
}
}
}
}
///
/// Given a source property path in segmented form, creates the correponding delegate that reads the
/// property value from the resource type instance
///
/// Generated expression tree
/// Parameter expression for provider wrapper
/// Resource type whose property is to be read
/// Collection of property names used for resource type property lookup
/// Index of current property name in
/// Property representing the final property in the path.
/// Expression tree which results in access to property value
private Expression BuildPropertyReader(Expression expr, Expression providerParam, ResourceType rsrcType, String[] srcPathSegments, int currentSegment, ref ResourceProperty rsrcProp)
{
if (currentSegment == srcPathSegments.Length)
{
return Expression.Convert(expr, WebUtil.GetTypeAllowingNull(expr.Type));
}
String srcPathPart = srcPathSegments[currentSegment];
rsrcProp = rsrcType != null ? rsrcType.TryResolvePropertyName(srcPathPart) : null;
// o.Prop
Expression objectDotProp;
// IProjectedResult.GetProjectedPropertyValue
Expression projectedResultGetProp;
if (rsrcProp != 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 (!rsrcProp.IsOfKind(currentSegment == srcPathSegments.Length - 1 ? ResourcePropertyKind.Primitive : ResourcePropertyKind.ComplexType))
{
throw new InvalidOperationException(Strings.EpmSourceTree_EndsWithNonPrimitiveType(srcPathPart));
}
// object IProjectedResult.GetProjectedPropertyValue(string)
projectedResultGetProp = Expression.Call(
Expression.Convert(expr, typeof(IProjectedResult)),
ResourceType.IProjectedResultGetProjectedPropertyValueMethodInfo,
Expression.Constant(rsrcProp.Name));
Type nullableResourcePropertyType = WebUtil.GetTypeAllowingNull(rsrcProp.Type);
projectedResultGetProp = Expression.Condition(
Expression.Equal(projectedResultGetProp, Expression.Constant(DBNull.Value)),
Expression.Constant(null, nullableResourcePropertyType),
Expression.Convert(projectedResultGetProp, nullableResourcePropertyType));
Debug.Assert(rsrcType != null, "rsrcType != null");
objectDotProp = Expression.Call(
providerParam,
ResourceType.GetPropertyValueMethodInfo,
expr,
Expression.Constant(rsrcProp),
Expression.Constant(rsrcType));
objectDotProp = Expression.Convert(objectDotProp, WebUtil.GetTypeAllowingNull(rsrcProp.Type));
}
else
{
if (rsrcType == null || rsrcType.IsOpenType)
{
// object IProjectedResult.GetProjectedPropertyValue(string)
projectedResultGetProp = Expression.Call(
Expression.Convert(expr, typeof(IProjectedResult)),
ResourceType.IProjectedResultGetProjectedPropertyValueMethodInfo,
Expression.Constant(srcPathPart));
projectedResultGetProp = Expression.Condition(
Expression.Equal(projectedResultGetProp, Expression.Constant(DBNull.Value)),
Expression.Constant(null, typeof(object)),
projectedResultGetProp);
// object WebUtil.GetNamedPropertyValue(object, string, provider)
objectDotProp = Expression.Call(
null, /* instance */
WebUtil.GetNamedPropertyValueMethodInfo,
expr,
Expression.Constant(srcPathPart),
providerParam);
}
else
{
throw new InvalidOperationException(Strings.EpmSourceTree_InaccessiblePropertyOnType(srcPathPart, rsrcType.Name));
}
}
Expression propertyAccess;
// IProjectedResult can occur only on the first level, as we don't allow projections into nav. properties
// or complex types.
// If it so happens that the source is a value type, it can't be IProjectedResult anyway.
if (currentSegment == 0 && !expr.Type.IsValueType)
{
if (!WebUtil.TypeAllowsNull(objectDotProp.Type))
{
objectDotProp = Expression.Convert(objectDotProp, WebUtil.GetTypeAllowingNull(objectDotProp.Type));
}
// if (o is IProjectedResult) then o.GetProjectedPropertyValue(); else o.Prop;
propertyAccess = Expression.Condition(
Expression.TypeIs(expr, typeof(IProjectedResult)),
projectedResultGetProp,
objectDotProp);
}
else
{
propertyAccess = objectDotProp;
}
Expression recursiveExpr = this.BuildPropertyReader(
propertyAccess,
providerParam,
rsrcProp != null ? rsrcProp.ResourceType : null,
srcPathSegments,
++currentSegment,
ref rsrcProp);
// o == null
BinaryExpression objectIsNull = Expression.Equal(expr, Expression.Constant(null));
ConstantExpression nullableNull = Expression.Constant(
null,
WebUtil.GetTypeAllowingNull(recursiveExpr.Type));
// if (o == null) then null; else o.Prop;
return Expression.Condition(objectIsNull, nullableNull, recursiveExpr);
}
///
/// Does given property in the attribute exist in this type or one of it's base types
///
/// Attribute which has PropertyName
/// true if property exists in current type, false otherwise
private bool PropertyExistsInCurrentType(EntityPropertyMappingAttribute epmAttr)
{
int indexOfSeparator = epmAttr.SourcePath.IndexOf('/');
String propertyToLookFor = indexOfSeparator == -1 ? epmAttr.SourcePath : epmAttr.SourcePath.Substring(0, indexOfSeparator);
return this.PropertiesDeclaredOnThisType.Any(p => p.Name == propertyToLookFor);
}
///
/// Checks if the resource type is sealed. If not, it throws an InvalidOperationException.
///
private void ThrowIfSealed()
{
if (this.isReadOnly)
{
throw new InvalidOperationException(Strings.ResourceType_Sealed(this.FullName));
}
}
///
/// Calls the virtual LoadPropertiesDeclaredOnThisType method, if its not already called and then
/// adds the properties returned by the method to the list of properties for this type.
///
private void GetPropertiesDeclaredOnThisType()
{
// We just call the virtual LoadPropertiesDeclaredOnThisType method only once. If it hasn't been called yet,
// then call the method and update the state to reflect that.
if (!this.isLoadPropertiesMethodCalled)
{
foreach (ResourceProperty p in this.LoadPropertiesDeclaredOnThisType())
{
this.AddPropertyInternal(p);
// if this type is already set to readonly, make sure that new properties returned by the virtual method
// are also set to readonly
if (this.IsReadOnly)
{
p.SetReadOnly();
}
}
this.isLoadPropertiesMethodCalled = true;
}
}
///
/// This method is called only when the Properties property is called and the type is already set to read-only.
/// This method validates all the properties w.r.t to the base type and calls SetReadOnly on all the properties.
///
private void ValidateType()
{
Debug.Assert(this.isLoadPropertiesMethodCalled && this.IsReadOnly, "This method must be invoked only if LoadPropertiesDeclaredOnThisType has been called and the type is set to ReadOnly");
if (this.BaseType != null)
{
// make sure that there are no properties with the same name. Properties with duplicate name within the type
// is already checked in AddProperty method
foreach (ResourceProperty rp in this.BaseType.Properties)
{
if (this.propertiesDeclaredOnThisType.Where(p => p.Name == rp.Name).FirstOrDefault() != null)
{
throw new InvalidOperationException(Strings.ResourceType_PropertyWithSameNameAlreadyExists(rp.Name, this.FullName));
}
}
}
else if (this.ResourceTypeKind == ResourceTypeKind.EntityType)
{
if (this.propertiesDeclaredOnThisType.Where(p => p.IsOfKind(ResourcePropertyKind.Key)).FirstOrDefault() == null)
{
throw new InvalidOperationException(Strings.ResourceType_MissingKeyPropertiesForEntity(this.FullName));
}
}
// set all the properties to readonly
foreach (ResourceProperty p in this.propertiesDeclaredOnThisType)
{
p.SetReadOnly();
// Note that we cache the propertyinfo objects for each CLR properties in the ResourceType class
// rather than the ResourceProperty class because the same ResourceProperty instance can be added
// to multiple ResourceType instances.
if (p.CanReflectOnInstanceTypeProperty)
{
this.GetPropertyInfoDecaredOnThisType(p);
}
}
// Resolve EpmInfos now that everything in the type hierarchy is readonly
try
{
if (this.EpmInfoInitialized == false)
{
this.BuildDynamicEpmInfo(this);
this.EpmInfoInitialized = true;
}
}
catch
{
// If an exception was thrown from this.BuildDynamicEpmInfo(this) method
// EpmSourceTree and EpmTargetTree may be only half constructed and need to be reset.
if (this.HasEntityPropertyMappings && !this.EpmInfoInitialized)
{
this.epmInfo.Reset();
}
throw;
}
}
#endregion Methods.
#region EpmInfoPerResourceType
/// Holder of Epm related data structure per resource type
private sealed class EpmInfoPerResourceType
{
/// EpmSourceTree per
private EpmSourceTree epmSourceTree;
/// EpmTargetTree per
private EpmTargetTree epmTargetTree;
/// Inherited EpmInfo
private List inheritedEpmInfo;
/// Own EpmInfo
private List ownEpmInfo;
/// Property for obtaining EpmSourceTree for a type
internal EpmSourceTree EpmSourceTree
{
get
{
if (this.epmSourceTree == null)
{
this.epmSourceTree = new EpmSourceTree(this.EpmTargetTree);
}
return this.epmSourceTree;
}
}
/// Property for obtaining EpmTargetTree for a type
internal EpmTargetTree EpmTargetTree
{
get
{
if (this.epmTargetTree == null)
{
this.epmTargetTree = new EpmTargetTree();
}
return this.epmTargetTree;
}
}
/// Inherited EpmInfo
internal List InheritedEpmInfo
{
get
{
if (this.inheritedEpmInfo == null)
{
this.inheritedEpmInfo = new List();
}
return this.inheritedEpmInfo;
}
}
/// Own EpmInfo
internal List OwnEpmInfo
{
get
{
if (this.ownEpmInfo == null)
{
this.ownEpmInfo = new List();
}
return this.ownEpmInfo;
}
}
///
/// Removes all data created internally by ResourceType. This is needed when building epm
/// info fails since the trees may be left in undefined state (i.e. half constructed) and
/// if inherited EPM attributes exist duplicates will be added.
///
internal void Reset()
{
this.epmTargetTree = null;
this.epmSourceTree = null;
this.inheritedEpmInfo = null;
}
}
#endregion
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.