Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataWeb / Server / System / Data / Services / Providers / ReflectionServiceProvider.cs / 1305376 / ReflectionServiceProvider.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides the interface definition for web data service
// data sources.
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services.Providers
{
#region Namespaces.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Services.Caching;
using System.Data.Services.Common;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
#endregion Namespaces.
///
/// Provides a reflection-based provider implementation.
///
[DebuggerDisplay("ReflectionServiceProvider: {Type}")]
internal class ReflectionServiceProvider : BaseServiceProvider
{
///
/// Initializes a new System.Data.Services.ReflectionServiceProvider instance.
///
/// Metadata for this provider.
/// data service instance.
internal ReflectionServiceProvider(MetadataCacheItem metadata, object dataServiceInstance)
: base(metadata, dataServiceInstance)
{
}
/// Gets a value indicating whether null propagation is required in expression trees.
public override bool IsNullPropagationRequired
{
get { return true; }
}
/// Namespace name for the EDM container.
public override string ContainerNamespace
{
get { return this.Type.Namespace; }
}
/// Name of the EDM container
public override string ContainerName
{
get { return this.Type.Name; }
}
///
/// Gets the ResourceAssociationSet instance when given the source association end.
///
/// Resource set of the source association end.
/// Resource type of the source association end.
/// Resource property of the source association end.
/// ResourceAssociationSet instance.
public override ResourceAssociationSet GetResourceAssociationSet(ResourceSet resourceSet, ResourceType resourceType, ResourceProperty resourceProperty)
{
Debug.Assert(resourceSet != null, "resourceSet != null");
Debug.Assert(resourceType != null, "resourceType != null");
Debug.Assert(resourceProperty != null, "resourceProperty != null");
Debug.Assert(resourceType == DataServiceProviderWrapper.GetDeclaringTypeForProperty(resourceType, resourceProperty), "resourceType should be the declaring type for resourceProperty");
ResourceType targetType = resourceProperty.ResourceType;
Debug.Assert(targetType != null && targetType.ResourceTypeKind == ResourceTypeKind.EntityType, "targetType != null && targetType.ResourceTypeKind == ResourceTypeKind.EntityType");
ResourceSet targetSet = InternalGetContainerForResourceType(targetType.InstanceType, this.EntitySets.Values);
Debug.Assert(targetSet != null, "targetSet != null");
string associationName = resourceType.Name + '_' + resourceProperty.Name;
// EF associations are first-class, navigation properties come second. So you actually
// define the two-way association first, then say that the nav props "go through" them.
// For CLR, however, there is no such constraint - in fact, we're very happy with one-way (link) associations.
// For one-way associations, the target property is always null.
ResourceAssociationSetEnd sourceEnd = new ResourceAssociationSetEnd(resourceSet, resourceType, resourceProperty);
ResourceAssociationSetEnd targetEnd = new ResourceAssociationSetEnd(targetSet, targetType, null);
return new ResourceAssociationSet(associationName, sourceEnd, targetEnd);
}
///
/// Checks whether the specified is ordered.
///
/// Type to check.
/// true if the type may be ordered; false otherwise.
///
/// The ordering may still fail at runtime; this method is currently
/// used for cleaner error messages only.
///
public override bool GetTypeIsOrdered(Type type)
{
Debug.Assert(type != null, "type != null");
if (typeof(IComparable).IsAssignableFrom(type))
{
return true;
}
else
{
return base.GetTypeIsOrdered(type);
}
}
/// Applies expansions and projections to the specified .
/// object to expand and apply projections to.
/// The root node of the tree which describes
/// the projections and expansions to be applied to the .
///
/// An object, with the results including
/// the expansions and projections specified in .
///
///
/// The returned may implement the interface
/// to provide enumerable objects for the expansions; otherwise, the expanded
/// information is expected to be found directly in the enumerated objects. If paging is
/// requested by providing a non-empty list in .OrderingInfo then
/// it is expected that the topmost would have a $skiptoken property
/// which will be an in itself and each of it's sub-properties will
/// be named SkipTokenPropertyXX where XX represents numbers in increasing order starting from 0. Each of
/// SkipTokenPropertyXX properties will be used to generated the $skiptoken to support paging.
/// If projections are required, the provider may choose to return
/// which returns instances of . In that case property values are determined
/// by calling the method instead of
/// accessing properties of the returned object directly.
/// If both expansion and projections are required, the provider may choose to return
/// of which in turn returns from its
/// property.
///
public override IQueryable ApplyProjections(
IQueryable source,
ProjectionNode projection)
{
Debug.Assert(projection is RootProjectionNode, "We always get the special root node.");
RootProjectionNode rootNode = (RootProjectionNode)projection;
Debug.Assert(rootNode.OrderingInfo != null, "We always get non-null OrderingInfo");
bool useBasicExpandProvider = ShouldUseBasicExpandProvider(rootNode);
// We need the $skiptoken for top level result if it is paged, hence we need to use ApplyExpansions in that case
if (useBasicExpandProvider || rootNode.OrderingInfo.IsPaged || rootNode.ProjectionsSpecified)
{
return new BasicExpandProvider(this.ProviderWrapper, true, true).ApplyProjections(source, projection);
}
// This pass-through implementation is appropriate for providers that fault-in on demand.
return BasicExpandProvider.ApplyOrderSkipTakeOnTopLevelResultBeforeProjections(
source,
rootNode.OrderingInfo,
rootNode.SkipCount,
rootNode.TakeCount);
}
#region IDataServiceQueryProvider Methods
///
/// Returns the collection of open properties name and value for the given resource instance.
///
/// instance of the resource.
/// Returns the collection of open properties name and value for the given resource instance. Currently not supported for Reflection provider.
public override IEnumerable> GetOpenPropertyValues(object target)
{
throw new NotImplementedException();
}
///
/// Gets the value of the open property.
///
/// instance of the resource type.
/// name of the property.
/// the value of the open property. Currently this is not supported for Reflection provider.
public override object GetOpenPropertyValue(object target, string propertyName)
{
throw new NotImplementedException();
}
#endregion IDataServiceQueryProvider Methods
/// Checks whether the given property is a key property.
/// property to check
/// returns the key kind of the property, based on the heuristic it matches
/// true if this is a key property, else returns false
internal static bool IsPropertyKeyProperty(PropertyInfo property, out ResourceKeyKind keyKind)
{
keyKind = (ResourceKeyKind)(-1);
// Only primitive types are allowed to be keys.
// Checks for generic to exclude Nullable<> value-type primitives, since we don't allows keys to be null.
if (WebUtil.IsPrimitiveType(property.PropertyType) &&
!property.PropertyType.IsGenericType)
{
DataServiceKeyAttribute keyAttribute = property.ReflectedType.GetCustomAttributes(true).OfType().FirstOrDefault();
if (keyAttribute != null && keyAttribute.KeyNames.Contains(property.Name))
{
keyKind = ResourceKeyKind.AttributedKey;
return true;
}
// For now, the key property must be {TypeName}Id or Id and the property
// type must be primitive, since we do not support non-primitive types
// as keys
if (property.Name == property.DeclaringType.Name + "ID")
{
keyKind = ResourceKeyKind.TypeNameId;
return true;
}
else if (property.Name == "ID")
{
keyKind = ResourceKeyKind.Id;
return true;
}
}
return false;
}
///
/// Checks whether the provider implements IUpdatable.
///
/// returns true if the provider implements IUpdatable. otherwise returns false.
internal override bool ImplementsIUpdatable()
{
return typeof(IUpdatable).IsAssignableFrom(this.Type);
}
/// Populates the metadata for this provider.
/// Dictionary of known CLR to ResourceType entries, which is populated as metadata is built.
/// list of already known types and their immediate children
/// Dictionary of name to ResourceSet for entity sets, populated as metadata is built.
protected override void PopulateMetadata(
IDictionary knownTypes,
IDictionary> childTypes,
IDictionary entitySets)
{
Queue unvisitedTypes = new Queue();
// Get the list of properties to be ignored.
List propertiesToBeIgnored = new List(
IgnorePropertiesAttribute.GetProperties(this.Type, true /*inherit*/, WebUtil.PublicInstanceBindingFlags));
PropertyInfo[] properties = this.Type.GetProperties(WebUtil.PublicInstanceBindingFlags);
foreach (PropertyInfo property in properties)
{
if (!propertiesToBeIgnored.Contains(property.Name) && property.CanRead && property.GetIndexParameters().Length == 0)
{
Type elementType = BaseServiceProvider.GetIQueryableElement(property.PropertyType);
if (elementType != null)
{
// If the element type has key defined (in itself or one of its ancestors)
ResourceType resourceType = BuildHierarchyForEntityType(elementType, knownTypes, childTypes, unvisitedTypes, true /* entity type candidate */);
if (resourceType != null)
{
// We do not allow MEST scenario for reflection provider
foreach (KeyValuePair entitySetInfo in entitySets)
{
Type entitySetType = entitySetInfo.Value.ResourceType.InstanceType;
if (entitySetType.IsAssignableFrom(elementType))
{
throw new InvalidOperationException(Strings.ReflectionProvider_MultipleEntitySetsForSameType(entitySetInfo.Value.Name, property.Name, entitySetType.FullName, resourceType.FullName));
}
else if (elementType.IsAssignableFrom(entitySetType))
{
throw new InvalidOperationException(Strings.ReflectionProvider_MultipleEntitySetsForSameType(property.Name, entitySetInfo.Value.Name, resourceType.FullName, entitySetType.FullName));
}
}
// Add the entity set to the list of entity sets.
ResourceSet resourceContainer = new ResourceSet(property.Name, resourceType);
entitySets.Add(property.Name, resourceContainer);
}
else
{
throw new InvalidOperationException(Strings.ReflectionProvider_InvalidEntitySetProperty(property.Name, XmlConvert.EncodeName(((IDataServiceMetadataProvider)this).ContainerName)));
}
}
}
}
// Populate the metadata for all the types in unvisited types
// and also their properties and populates metadata about property types
PopulateMetadataForTypes(knownTypes, childTypes, unvisitedTypes, entitySets.Values);
// At this point, we should have all the top level entity types and the complex types
PopulateMetadataForDerivedTypes(knownTypes, childTypes, unvisitedTypes, entitySets.Values);
// Populate and initialize the EntityPropertyMappingInfos for the data context
foreach (ResourceType resourceType in knownTypes.Values)
{
resourceType.BuildReflectionEpmInfo(resourceType);
resourceType.EpmInfoInitialized = true;
}
}
///
/// Creates the IQueryable instance for the given resource set and returns it
///
/// resource set for which IQueryable instance needs to be created
/// returns the IQueryable instance for the given resource set
protected override IQueryable GetResourceContainerInstance(ResourceSet resourceContainer)
{
Debug.Assert(resourceContainer != null, "resourceContainer != null");
if (resourceContainer.ReadFromContextDelegate == null)
{
PropertyInfo propertyInfo = this.Type.GetProperty(resourceContainer.Name, WebUtil.PublicInstanceBindingFlags);
MethodInfo getValueMethod = propertyInfo.GetGetMethod();
// return ((TheContext)arg0).get_Property();
Type[] parameterTypes = new Type[] { typeof(object) };
System.Reflection.Emit.DynamicMethod readerMethod = new System.Reflection.Emit.DynamicMethod("queryable_reader", typeof(IQueryable), parameterTypes, false);
var generator = readerMethod.GetILGenerator();
generator.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);
generator.Emit(System.Reflection.Emit.OpCodes.Castclass, this.Type);
generator.Emit(System.Reflection.Emit.OpCodes.Call, getValueMethod);
generator.Emit(System.Reflection.Emit.OpCodes.Ret);
resourceContainer.ReadFromContextDelegate = (Func)readerMethod.CreateDelegate(typeof(Func));
}
Debug.Assert(resourceContainer.ReadFromContextDelegate != null, "resourceContainer.ReadFromContextDelegate != null");
return resourceContainer.ReadFromContextDelegate(this.CurrentDataSource);
}
///
/// Populate types for metadata specified by the provider
///
/// list of types specified by the provider
/// list of already known types
/// list of already known types and their immediate children
/// list of entity sets as specified in the data source type
protected override void PopulateMetadataForUserSpecifiedTypes(
IEnumerable userSpecifiedTypes,
IDictionary knownTypes,
IDictionary> childTypes,
IEnumerable entitySets)
{
Queue unvisitedTypes = new Queue();
foreach (Type type in userSpecifiedTypes)
{
ResourceType resourceType;
if (TryGetType(knownTypes, type, out resourceType))
{
continue;
}
if (IsEntityOrComplexType(type, knownTypes, childTypes, unvisitedTypes) == null)
{
throw new InvalidOperationException(Strings.BadProvider_InvalidTypeSpecified(type.FullName));
}
}
PopulateMetadataForTypes(knownTypes, childTypes, unvisitedTypes, entitySets);
}
///
/// Populate metadata for the given clr type.
///
/// type whose metadata needs to be loaded.
/// list of already known resource types.
/// list of already known types and their immediate children
/// list of entity sets as specified in the data source.
/// resource type containing metadata for the given clr type.
protected override ResourceType PopulateMetadataForType(
Type type,
IDictionary knownTypes,
IDictionary> childTypes,
IEnumerable entitySets)
{
Queue unvisitedTypes = new Queue();
ResourceType resourceType;
if (!TryGetType(knownTypes, type, out resourceType))
{
resourceType = IsEntityOrComplexType(type, knownTypes, childTypes, unvisitedTypes);
if (resourceType != null)
{
PopulateMetadataForTypes(knownTypes, childTypes, unvisitedTypes, entitySets);
}
}
return resourceType;
}
/// Checks whether the specified type is a complex type.
/// Type to check.
///
/// true if the specified type is a complex type; false otherwise. Note
/// that resources are not distinguished from complex types.
///
private static bool IsComplexType(Type type)
{
Debug.Assert(type != null, "type != null");
// Complex types are all types that contain public properties of primitive
// types.
//
// We purposefully ignore certain known classes which fit this description
// but we know are not meaningful for Astoria:
// - System.Array: what would get serialized would be Length, IsFixed, etc.
// - Pointers: we would otherwise serialize the pointer size
// - COM object wrappers
// - interface: since we will never know what the exact type of the instance will be.
if (!type.IsVisible || type.IsArray || type.IsPointer || type.IsCOMObject || type.IsInterface ||
type == typeof(IntPtr) || type == typeof(UIntPtr) || type == typeof(char) ||
type == typeof(TimeSpan) || type == typeof(DateTimeOffset) || type == typeof(Uri) ||
type.IsEnum)
{
return false;
}
return true;
}
///
/// Checks whether there is a key defined for the given type.
///
/// type to check
///
/// Whether is being considered as a possible
/// entity type.
///
/// returns true if there are one or key properties present else returns false
private static bool DoesTypeHaveKeyProperties(Type type, bool entityTypeCandidate)
{
Debug.Assert(type != null, "type != null");
// Check for properties declared on this element only
foreach (PropertyInfo property in type.GetProperties(WebUtil.PublicInstanceBindingFlags | BindingFlags.DeclaredOnly))
{
ResourceKeyKind keyKind;
if (IsPropertyKeyProperty(property, out keyKind))
{
if (keyKind == ResourceKeyKind.AttributedKey && !entityTypeCandidate)
{
throw new InvalidOperationException(Strings.ReflectionProvider_EntityTypeHasKeyButNoEntitySet(type.FullName));
}
if (!entityTypeCandidate)
{
return false;
}
return true;
}
}
return false;
}
///
/// Populates the metadata for the given unvisited types and all the associated types with this type
///
/// list of known types
/// list of already known types and their immediate children
/// list of unvisited type
/// Available entity sets.
private static void PopulateMetadataForTypes(
IDictionary knownTypes,
IDictionary> childTypes,
Queue unvisitedTypes,
IEnumerable entitySets)
{
Debug.Assert(knownTypes != null, "knownTypes != null");
Debug.Assert(unvisitedTypes != null, "unvisitedTypes != null");
Debug.Assert(entitySets != null, "entitySets != null");
// Start walking down all the types
while (unvisitedTypes.Count != 0)
{
// get the unvisited element
ResourceType type = unvisitedTypes.Dequeue();
// Go through all the properties and find out one or more complex types
BuildTypeProperties(type, knownTypes, childTypes, unvisitedTypes, entitySets);
}
}
///
/// Walks through the list of ancestors and finds the root base type and collects metadata for the entire chain of ancestors
///
/// type whose ancestors metadata needs to be populated
/// list of already known types
/// list of already known types and their immediate children
/// list of unvisited types
/// Whether is a candidate to be an entity type.
/// return true if this given type is a entity type, otherwise returns false
private static ResourceType BuildHierarchyForEntityType(
Type type,
IDictionary knownTypes,
IDictionary> childTypes,
Queue unvisitedTypes,
bool entityTypeCandidate)
{
List ancestors = new List();
if (!type.IsVisible)
{
return null;
}
if (CommonUtil.IsUnsupportedType(type))
{
// deriving from an unsupported type is not allowed
throw new InvalidOperationException(Strings.BadProvider_UnsupportedType(type.FullName));
}
Type baseType = type;
ResourceType baseResourceType = null;
// Since this method is also used on property types, which can be interfaces,
// Base types can be null
while (baseType != null)
{
// Try and check if the base type is already loaded
if (TryGetType(knownTypes, baseType, out baseResourceType))
{
break;
}
ancestors.Add(baseType);
baseType = baseType.BaseType;
}
if (baseResourceType == null)
{
// If entityTypeCandidate is false, then it means that the current type can't
// be a entity type with keys. In other words, it must derive from an existing
// type. Otherwise, its not an entity type
if (entityTypeCandidate == false)
{
return null;
}
// Find the last ancestor which has key defined
for (int i = ancestors.Count - 1; i >= 0; i--)
{
if (CommonUtil.IsUnsupportedType(ancestors[i]))
{
// deriving from an unsupported type is not allowed
throw new InvalidOperationException(Strings.BadProvider_UnsupportedAncestorType(type.FullName, ancestors[i].FullName));
}
if (DoesTypeHaveKeyProperties(ancestors[i], entityTypeCandidate))
{
break;
}
// Else this type is not interesting. Remove it from the ancestors list
ancestors.RemoveAt(i);
}
}
else if (baseResourceType.ResourceTypeKind != ResourceTypeKind.EntityType)
{
return null;
}
else if (ancestors.Count == 0)
{
// we might have found the top level element.So just return
return baseResourceType;
}
// For all the valid ancestors, add the type to the list of types encountered
// and unvisited types
// its important that we enqueue the ancestors first, since when we populate member metadata
// we can make sure that the base type is fully populated
for (int i = ancestors.Count - 1; i >= 0; i--)
{
ResourceType entityType = ReflectionServiceProvider.CreateResourceType(ancestors[i], ResourceTypeKind.EntityType, baseResourceType, knownTypes, childTypes);
unvisitedTypes.Enqueue(entityType);
baseResourceType = entityType;
}
return baseResourceType;
}
///
/// Populates the metadata for the properties of the given resource type
///
/// resource type whose properties metadata needs to be populated
/// list of known types
/// list of already known types and their immediate children
/// list of unvisited type
/// Available entity sets.
private static void BuildTypeProperties(
ResourceType parentResourceType,
IDictionary knownTypes,
IDictionary> childTypes,
Queue unvisitedTypes,
IEnumerable entitySets)
{
Debug.Assert(parentResourceType != null, "parentResourceType != null");
Debug.Assert(knownTypes != null, "knownTypes != null");
Debug.Assert(unvisitedTypes != null, "unvisitedTypes != null");
Debug.Assert(entitySets != null, "entitySets != null");
BindingFlags bindingFlags = WebUtil.PublicInstanceBindingFlags;
// For non root types, we should only look for properties that are declared for this type
if (parentResourceType.BaseType != null)
{
bindingFlags = bindingFlags | BindingFlags.DeclaredOnly;
}
HashSet propertiesToBeIgnored = new HashSet(IgnorePropertiesAttribute.GetProperties(parentResourceType.InstanceType, false /*inherit*/, bindingFlags), StringComparer.Ordinal);
Debug.Assert(parentResourceType.IsOpenType == false, "ReflectionServiceProvider does not support Open types.");
HashSet etagPropertyNames = new HashSet(LoadETagProperties(parentResourceType), StringComparer.Ordinal);
ResourceKeyKind keyKind = (ResourceKeyKind)Int32.MaxValue;
PropertyInfo[] properties = parentResourceType.InstanceType.GetProperties(bindingFlags);
foreach (PropertyInfo property in properties)
{
// Ignore the properties which are specified in the IgnoreProperties attribute
if (propertiesToBeIgnored.Contains(property.Name))
{
continue;
}
if (property.CanRead && property.GetIndexParameters().Length == 0)
{
ResourcePropertyKind kind = (ResourcePropertyKind)(-1);
ResourceKeyKind currentKeyKind = (ResourceKeyKind)(-1);
ResourceType resourceType;
Type resourcePropertyType = property.PropertyType;
ResourceSet container = null;
bool collection = false;
if (!TryGetType(knownTypes, resourcePropertyType, out resourceType))
{
Type collectionType = GetIEnumerableElement(property.PropertyType);
if (collectionType != null)
{
TryGetType(knownTypes, collectionType, out resourceType);
// Even if the above method returns false, we should set the
// following variable appropriately, so that we can use them below
collection = true;
resourcePropertyType = collectionType;
}
}
if (resourceType != null)
{
#region Already Known Type
if (resourceType.ResourceTypeKind == ResourceTypeKind.Primitive)
{
// Check for key property only on root types, since keys must be defined on the root types
if (parentResourceType.BaseType == null && parentResourceType.ResourceTypeKind == ResourceTypeKind.EntityType && IsPropertyKeyProperty(property, out currentKeyKind))
{
if ((int)currentKeyKind < (int)keyKind)
{
if (parentResourceType.KeyProperties.Count != 0)
{
// Remove the existing property as key property - mark it as non key property
parentResourceType.RemoveKeyProperties();
}
keyKind = currentKeyKind;
kind = ResourcePropertyKind.Key | ResourcePropertyKind.Primitive;
}
else if ((int)currentKeyKind == (int)keyKind)
{
Debug.Assert(currentKeyKind == ResourceKeyKind.AttributedKey, "This is the only way of specifying composite keys");
kind = ResourcePropertyKind.Key | ResourcePropertyKind.Primitive;
}
else
{
kind = ResourcePropertyKind.Primitive;
}
}
else
{
kind = ResourcePropertyKind.Primitive;
}
}
else if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType)
{
kind = ResourcePropertyKind.ComplexType;
}
else if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
kind = collection ? ResourcePropertyKind.ResourceSetReference : ResourcePropertyKind.ResourceReference;
}
#endregion // Already Known Type
}
else
{
resourceType = IsEntityOrComplexType(resourcePropertyType, knownTypes, childTypes, unvisitedTypes);
if (resourceType != null)
{
if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType)
{
kind = ResourcePropertyKind.ComplexType;
}
else
{
Debug.Assert(resourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "Must be an entity type");
kind = collection ? ResourcePropertyKind.ResourceSetReference : ResourcePropertyKind.ResourceReference;
}
}
}
// if resource type is null OR
// if resource type is a collection of primitive or complex types OR
// if complex type has a property of entity type
if (resourceType == null ||
(resourceType.ResourceTypeKind != ResourceTypeKind.EntityType && collection) ||
(resourceType.ResourceTypeKind == ResourceTypeKind.EntityType && parentResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType))
{
if (resourceType == null)
{
if (CommonUtil.IsUnsupportedType(resourcePropertyType))
{
throw new InvalidOperationException(Strings.BadProvider_UnsupportedPropertyType(property.Name, parentResourceType.FullName));
}
throw new InvalidOperationException(Strings.ReflectionProvider_InvalidProperty(property.Name, parentResourceType.FullName));
}
else
{
// collection of complex types not supported
throw new InvalidOperationException(Strings.ReflectionProvider_CollectionOfPrimitiveOrComplexNotSupported(property.Name, parentResourceType.FullName));
}
}
if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
container = InternalGetContainerForResourceType(resourcePropertyType, entitySets);
if (container == null)
{
throw new InvalidOperationException(Strings.ReflectionProvider_EntityPropertyWithNoEntitySet(parentResourceType.FullName, property.Name));
}
}
if (etagPropertyNames.Remove(property.Name))
{
kind |= ResourcePropertyKind.ETag;
}
ResourceProperty resourceProperty = new ResourceProperty(property.Name, kind, resourceType);
MimeTypeAttribute attribute = MimeTypeAttribute.GetMimeTypeAttribute(property);
if (attribute != null)
{
resourceProperty.MimeType = attribute.MimeType;
}
parentResourceType.AddProperty(resourceProperty);
}
else
{
throw new InvalidOperationException(Strings.ReflectionProvider_InvalidProperty(property.Name, parentResourceType.FullName));
}
}
if (parentResourceType.ResourceTypeKind == ResourceTypeKind.EntityType &&
(parentResourceType.KeyProperties == null || parentResourceType.KeyProperties.Count == 0))
{
throw new InvalidOperationException(Strings.ReflectionProvider_KeyPropertiesCannotBeIgnored(parentResourceType.FullName));
}
if (etagPropertyNames.Count != 0)
{
throw new InvalidOperationException(Strings.ReflectionProvider_ETagPropertyNameNotValid(etagPropertyNames.ElementAt(0), parentResourceType.FullName));
}
}
///
/// If the given type is a entity or complex type, it returns the resource type corresponding to the given type
///
/// clr type
/// list of already known types
/// list of already known types and their immediate children
/// list of unvisited types
/// resource type corresponding to the given clr type, if the clr type is entity or complex
private static ResourceType IsEntityOrComplexType(
Type type,
IDictionary knownTypes,
IDictionary> childTypes,
Queue unvisitedTypes)
{
// Ignore values types here. We do not support resources of values type (entity or complex)
if (type.IsValueType || CommonUtil.IsUnsupportedType(type))
{
return null;
}
ResourceType resourceType = BuildHierarchyForEntityType(type, knownTypes, childTypes, unvisitedTypes, false /* entityTypeCandidate */);
if (resourceType == null && IsComplexType(type))
{
resourceType = ReflectionServiceProvider.CreateResourceType(type, ResourceTypeKind.ComplexType, null, knownTypes, childTypes);
unvisitedTypes.Enqueue(resourceType);
}
return resourceType;
}
/// Get the resource set for the given clr type.
/// clr type for which resource set name needs to be returned
/// Available entity sets to consider.
/// The container for its type, null if not found.
private static ResourceSet InternalGetContainerForResourceType(Type type, IEnumerable entitySets)
{
Debug.Assert(type != null, "type != null");
Debug.Assert(entitySets != null, "entitySets != null");
// For each entity set, find out which one matches the type of this resource
foreach (ResourceSet entitySetInfo in entitySets)
{
if (entitySetInfo.ResourceType.InstanceType.IsAssignableFrom(type))
{
return entitySetInfo;
}
}
return null;
}
///
/// Find out all the derived types in the list of assemblies and then populate metadata for those types
///
/// list of known types
/// list of already known types and their immediate children
/// list of unvisited types
/// Available entity sets.
private static void PopulateMetadataForDerivedTypes(
IDictionary knownTypes,
IDictionary> childTypes,
Queue unvisitedTypes,
IEnumerable entitySets)
{
Debug.Assert(knownTypes != null, "knownTypes != null");
Debug.Assert(unvisitedTypes != null, "unvisitedTypes != null");
Debug.Assert(entitySets != null, "entitySets != null");
// Find all the root resource entity types
List rootTypes = new List();
foreach (ResourceType resourceType in knownTypes.Values)
{
if (resourceType.BaseType == null &&
resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
rootTypes.Add(resourceType);
}
}
// Use the default comparer, which calls Assembly.Equals (not a simple reference comparison).
HashSet assemblies = new HashSet(EqualityComparer.Default);
List derivedTypes = new List();
// Walk through all the types in the assemblies and find all the derived types
foreach (ResourceType resourceType in knownTypes.Values)
{
// No need to look into primitive types, as these live in system assemblies.
if (resourceType.ResourceTypeKind == ResourceTypeKind.Primitive)
{
continue;
}
Assembly assembly = resourceType.InstanceType.Assembly;
//// ignore if the assembly has already been scanned
if (assemblies.Contains(assembly))
{
continue;
}
// Walk all the types in that assembly
foreach (Type type in assembly.GetTypes())
{
// skip all the non visible types or types which have generic parameters
if (!type.IsVisible || HasGenericParameters(type))
{
continue;
}
// Skip the type if its already loaded
if (knownTypes.ContainsKey(type))
{
continue;
}
// Check if this type dervies from any one of the root types
for (int i = 0; i < rootTypes.Count; i++)
{
if (rootTypes[i].InstanceType.IsAssignableFrom(type))
{
derivedTypes.Add(type);
}
}
}
assemblies.Add(assembly);
}
foreach (Type type in derivedTypes)
{
BuildHierarchyForEntityType(type, knownTypes, childTypes, unvisitedTypes, false /* entityTypeCandidate */);
PopulateMetadataForTypes(knownTypes, childTypes, unvisitedTypes, entitySets);
}
}
///
/// Loads the etag properties for the given resource type
///
/// resource type whose etag property names need to be loaded.
/// the list of properties that form the etag for the given resource type.
private static IEnumerable LoadETagProperties(ResourceType resourceType)
{
// if it is the root type, then we need to inherit the attribute from the base type.
// otherwise, we need not, since if the base type already has it, the appropriate properties
// must already have been marked as concurrency properties.
bool inherit = resourceType.BaseType == null;
// Read the etag attribute from the type and return it
ETagAttribute[] attributes = (ETagAttribute[])resourceType.InstanceType.GetCustomAttributes(typeof(ETagAttribute), inherit);
Debug.Assert(attributes.Length <= 1, "Only one attribute can be specified per type");
if (attributes.Length == 1)
{
// Validate the property names
// we may need to cache them instead of reading them everytime
return attributes[0].PropertyNames;
}
return WebUtil.EmptyStringArray;
}
///
/// returns the new resource type instance
///
/// backing clr type for the resource.
/// kind of the resource.
/// base type of the resource.
/// list of already known types
/// list of already known types and their immediate children
/// returns a new instance of the resource type containing all the metadata.
private static ResourceType CreateResourceType(
Type type,
ResourceTypeKind kind,
ResourceType baseType,
IDictionary knownTypes,
IDictionary> childTypes)
{
ResourceType resourceType = new ResourceType(type, kind, baseType, type.Namespace, GetModelTypeName(type), type.IsAbstract);
resourceType.IsOpenType = false;
// We need to look at inherited attributes as well so we pass true for inherit argument.
if (type.GetCustomAttributes(typeof(HasStreamAttribute), true /* inherit */).Length == 1)
{
resourceType.IsMediaLinkEntry = true;
}
knownTypes.Add(type, resourceType);
childTypes.Add(resourceType, null);
if (baseType != null)
{
Debug.Assert(childTypes.ContainsKey(baseType), "childTypes.ContainsKey(baseType)");
if (childTypes[baseType] == null)
{
childTypes[baseType] = new List();
}
childTypes[baseType].Add(resourceType);
}
return resourceType;
}
///
/// Gets the type name (without namespace) of the specified ,
/// appropriate as an externally-visible type name.
///
/// Type to get name for.
/// The type name for .
private static string GetModelTypeName(Type type)
{
Debug.Assert(type != null, "type != null");
if (type.IsGenericType)
{
Type[] genericArguments = type.GetGenericArguments();
StringBuilder builder = new StringBuilder(type.Name.Length * 2 * (1 + genericArguments.Length));
if (type.IsNested)
{
Debug.Assert(type.DeclaringType != null, "type.DeclaringType != null");
builder.Append(GetModelTypeName(type.DeclaringType));
builder.Append('_');
}
builder.Append(type.Name);
builder.Append('[');
for (int i = 0; i < genericArguments.Length; i++)
{
if (i > 0)
{
builder.Append(' ');
}
string genericNamespace = WebUtil.GetModelTypeNamespace(genericArguments[i]);
if (!String.IsNullOrEmpty(genericNamespace))
{
builder.Append(genericNamespace);
builder.Append('.');
}
builder.Append(GetModelTypeName(genericArguments[i]));
}
builder.Append(']');
return builder.ToString();
}
else if (type.IsNested)
{
Debug.Assert(type.DeclaringType != null, "type.DeclaringType != null");
return GetModelTypeName(type.DeclaringType) + "_" + type.Name;
}
else
{
return type.Name;
}
}
///
/// Checks whether the given type is a generic type with a generic parameter.
///
/// type which needs to be checked.
/// Returns true, if the is generic and has generic parameters. Otherwise returns false.
private static bool HasGenericParameters(Type type)
{
if (type.IsGenericType)
{
foreach (Type arg in type.GetGenericArguments())
{
if (arg.IsGenericParameter)
{
return true;
}
}
}
return false;
}
///
/// Returns true if the subtree of expansions rooted in the specified
/// contains either a filter or paging/maxresult constraint.
///
/// The root of the expansions tree to inspect.
/// True if BasicExpandProvider should be used to process a query with this tree
/// or false otherwise.
private static bool ShouldUseBasicExpandProvider(ExpandedProjectionNode expandedNode)
{
foreach (ProjectionNode node in expandedNode.Nodes)
{
ExpandedProjectionNode childExpandedNode = node as ExpandedProjectionNode;
if (childExpandedNode != null)
{
if (childExpandedNode.HasFilterOrMaxResults)
{
return true;
}
if (ShouldUseBasicExpandProvider(childExpandedNode))
{
return true;
}
}
}
return false;
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides the interface definition for web data service
// data sources.
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services.Providers
{
#region Namespaces.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Services.Caching;
using System.Data.Services.Common;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
#endregion Namespaces.
///
/// Provides a reflection-based provider implementation.
///
[DebuggerDisplay("ReflectionServiceProvider: {Type}")]
internal class ReflectionServiceProvider : BaseServiceProvider
{
///
/// Initializes a new System.Data.Services.ReflectionServiceProvider instance.
///
/// Metadata for this provider.
/// data service instance.
internal ReflectionServiceProvider(MetadataCacheItem metadata, object dataServiceInstance)
: base(metadata, dataServiceInstance)
{
}
/// Gets a value indicating whether null propagation is required in expression trees.
public override bool IsNullPropagationRequired
{
get { return true; }
}
/// Namespace name for the EDM container.
public override string ContainerNamespace
{
get { return this.Type.Namespace; }
}
/// Name of the EDM container
public override string ContainerName
{
get { return this.Type.Name; }
}
///
/// Gets the ResourceAssociationSet instance when given the source association end.
///
/// Resource set of the source association end.
/// Resource type of the source association end.
/// Resource property of the source association end.
/// ResourceAssociationSet instance.
public override ResourceAssociationSet GetResourceAssociationSet(ResourceSet resourceSet, ResourceType resourceType, ResourceProperty resourceProperty)
{
Debug.Assert(resourceSet != null, "resourceSet != null");
Debug.Assert(resourceType != null, "resourceType != null");
Debug.Assert(resourceProperty != null, "resourceProperty != null");
Debug.Assert(resourceType == DataServiceProviderWrapper.GetDeclaringTypeForProperty(resourceType, resourceProperty), "resourceType should be the declaring type for resourceProperty");
ResourceType targetType = resourceProperty.ResourceType;
Debug.Assert(targetType != null && targetType.ResourceTypeKind == ResourceTypeKind.EntityType, "targetType != null && targetType.ResourceTypeKind == ResourceTypeKind.EntityType");
ResourceSet targetSet = InternalGetContainerForResourceType(targetType.InstanceType, this.EntitySets.Values);
Debug.Assert(targetSet != null, "targetSet != null");
string associationName = resourceType.Name + '_' + resourceProperty.Name;
// EF associations are first-class, navigation properties come second. So you actually
// define the two-way association first, then say that the nav props "go through" them.
// For CLR, however, there is no such constraint - in fact, we're very happy with one-way (link) associations.
// For one-way associations, the target property is always null.
ResourceAssociationSetEnd sourceEnd = new ResourceAssociationSetEnd(resourceSet, resourceType, resourceProperty);
ResourceAssociationSetEnd targetEnd = new ResourceAssociationSetEnd(targetSet, targetType, null);
return new ResourceAssociationSet(associationName, sourceEnd, targetEnd);
}
///
/// Checks whether the specified is ordered.
///
/// Type to check.
/// true if the type may be ordered; false otherwise.
///
/// The ordering may still fail at runtime; this method is currently
/// used for cleaner error messages only.
///
public override bool GetTypeIsOrdered(Type type)
{
Debug.Assert(type != null, "type != null");
if (typeof(IComparable).IsAssignableFrom(type))
{
return true;
}
else
{
return base.GetTypeIsOrdered(type);
}
}
/// Applies expansions and projections to the specified .
/// object to expand and apply projections to.
/// The root node of the tree which describes
/// the projections and expansions to be applied to the .
///
/// An object, with the results including
/// the expansions and projections specified in .
///
///
/// The returned may implement the interface
/// to provide enumerable objects for the expansions; otherwise, the expanded
/// information is expected to be found directly in the enumerated objects. If paging is
/// requested by providing a non-empty list in .OrderingInfo then
/// it is expected that the topmost would have a $skiptoken property
/// which will be an in itself and each of it's sub-properties will
/// be named SkipTokenPropertyXX where XX represents numbers in increasing order starting from 0. Each of
/// SkipTokenPropertyXX properties will be used to generated the $skiptoken to support paging.
/// If projections are required, the provider may choose to return
/// which returns instances of . In that case property values are determined
/// by calling the method instead of
/// accessing properties of the returned object directly.
/// If both expansion and projections are required, the provider may choose to return
/// of which in turn returns from its
/// property.
///
public override IQueryable ApplyProjections(
IQueryable source,
ProjectionNode projection)
{
Debug.Assert(projection is RootProjectionNode, "We always get the special root node.");
RootProjectionNode rootNode = (RootProjectionNode)projection;
Debug.Assert(rootNode.OrderingInfo != null, "We always get non-null OrderingInfo");
bool useBasicExpandProvider = ShouldUseBasicExpandProvider(rootNode);
// We need the $skiptoken for top level result if it is paged, hence we need to use ApplyExpansions in that case
if (useBasicExpandProvider || rootNode.OrderingInfo.IsPaged || rootNode.ProjectionsSpecified)
{
return new BasicExpandProvider(this.ProviderWrapper, true, true).ApplyProjections(source, projection);
}
// This pass-through implementation is appropriate for providers that fault-in on demand.
return BasicExpandProvider.ApplyOrderSkipTakeOnTopLevelResultBeforeProjections(
source,
rootNode.OrderingInfo,
rootNode.SkipCount,
rootNode.TakeCount);
}
#region IDataServiceQueryProvider Methods
///
/// Returns the collection of open properties name and value for the given resource instance.
///
/// instance of the resource.
/// Returns the collection of open properties name and value for the given resource instance. Currently not supported for Reflection provider.
public override IEnumerable> GetOpenPropertyValues(object target)
{
throw new NotImplementedException();
}
///
/// Gets the value of the open property.
///
/// instance of the resource type.
/// name of the property.
/// the value of the open property. Currently this is not supported for Reflection provider.
public override object GetOpenPropertyValue(object target, string propertyName)
{
throw new NotImplementedException();
}
#endregion IDataServiceQueryProvider Methods
/// Checks whether the given property is a key property.
/// property to check
/// returns the key kind of the property, based on the heuristic it matches
/// true if this is a key property, else returns false
internal static bool IsPropertyKeyProperty(PropertyInfo property, out ResourceKeyKind keyKind)
{
keyKind = (ResourceKeyKind)(-1);
// Only primitive types are allowed to be keys.
// Checks for generic to exclude Nullable<> value-type primitives, since we don't allows keys to be null.
if (WebUtil.IsPrimitiveType(property.PropertyType) &&
!property.PropertyType.IsGenericType)
{
DataServiceKeyAttribute keyAttribute = property.ReflectedType.GetCustomAttributes(true).OfType().FirstOrDefault();
if (keyAttribute != null && keyAttribute.KeyNames.Contains(property.Name))
{
keyKind = ResourceKeyKind.AttributedKey;
return true;
}
// For now, the key property must be {TypeName}Id or Id and the property
// type must be primitive, since we do not support non-primitive types
// as keys
if (property.Name == property.DeclaringType.Name + "ID")
{
keyKind = ResourceKeyKind.TypeNameId;
return true;
}
else if (property.Name == "ID")
{
keyKind = ResourceKeyKind.Id;
return true;
}
}
return false;
}
///
/// Checks whether the provider implements IUpdatable.
///
/// returns true if the provider implements IUpdatable. otherwise returns false.
internal override bool ImplementsIUpdatable()
{
return typeof(IUpdatable).IsAssignableFrom(this.Type);
}
/// Populates the metadata for this provider.
/// Dictionary of known CLR to ResourceType entries, which is populated as metadata is built.
/// list of already known types and their immediate children
/// Dictionary of name to ResourceSet for entity sets, populated as metadata is built.
protected override void PopulateMetadata(
IDictionary knownTypes,
IDictionary> childTypes,
IDictionary entitySets)
{
Queue unvisitedTypes = new Queue();
// Get the list of properties to be ignored.
List propertiesToBeIgnored = new List(
IgnorePropertiesAttribute.GetProperties(this.Type, true /*inherit*/, WebUtil.PublicInstanceBindingFlags));
PropertyInfo[] properties = this.Type.GetProperties(WebUtil.PublicInstanceBindingFlags);
foreach (PropertyInfo property in properties)
{
if (!propertiesToBeIgnored.Contains(property.Name) && property.CanRead && property.GetIndexParameters().Length == 0)
{
Type elementType = BaseServiceProvider.GetIQueryableElement(property.PropertyType);
if (elementType != null)
{
// If the element type has key defined (in itself or one of its ancestors)
ResourceType resourceType = BuildHierarchyForEntityType(elementType, knownTypes, childTypes, unvisitedTypes, true /* entity type candidate */);
if (resourceType != null)
{
// We do not allow MEST scenario for reflection provider
foreach (KeyValuePair entitySetInfo in entitySets)
{
Type entitySetType = entitySetInfo.Value.ResourceType.InstanceType;
if (entitySetType.IsAssignableFrom(elementType))
{
throw new InvalidOperationException(Strings.ReflectionProvider_MultipleEntitySetsForSameType(entitySetInfo.Value.Name, property.Name, entitySetType.FullName, resourceType.FullName));
}
else if (elementType.IsAssignableFrom(entitySetType))
{
throw new InvalidOperationException(Strings.ReflectionProvider_MultipleEntitySetsForSameType(property.Name, entitySetInfo.Value.Name, resourceType.FullName, entitySetType.FullName));
}
}
// Add the entity set to the list of entity sets.
ResourceSet resourceContainer = new ResourceSet(property.Name, resourceType);
entitySets.Add(property.Name, resourceContainer);
}
else
{
throw new InvalidOperationException(Strings.ReflectionProvider_InvalidEntitySetProperty(property.Name, XmlConvert.EncodeName(((IDataServiceMetadataProvider)this).ContainerName)));
}
}
}
}
// Populate the metadata for all the types in unvisited types
// and also their properties and populates metadata about property types
PopulateMetadataForTypes(knownTypes, childTypes, unvisitedTypes, entitySets.Values);
// At this point, we should have all the top level entity types and the complex types
PopulateMetadataForDerivedTypes(knownTypes, childTypes, unvisitedTypes, entitySets.Values);
// Populate and initialize the EntityPropertyMappingInfos for the data context
foreach (ResourceType resourceType in knownTypes.Values)
{
resourceType.BuildReflectionEpmInfo(resourceType);
resourceType.EpmInfoInitialized = true;
}
}
///
/// Creates the IQueryable instance for the given resource set and returns it
///
/// resource set for which IQueryable instance needs to be created
/// returns the IQueryable instance for the given resource set
protected override IQueryable GetResourceContainerInstance(ResourceSet resourceContainer)
{
Debug.Assert(resourceContainer != null, "resourceContainer != null");
if (resourceContainer.ReadFromContextDelegate == null)
{
PropertyInfo propertyInfo = this.Type.GetProperty(resourceContainer.Name, WebUtil.PublicInstanceBindingFlags);
MethodInfo getValueMethod = propertyInfo.GetGetMethod();
// return ((TheContext)arg0).get_Property();
Type[] parameterTypes = new Type[] { typeof(object) };
System.Reflection.Emit.DynamicMethod readerMethod = new System.Reflection.Emit.DynamicMethod("queryable_reader", typeof(IQueryable), parameterTypes, false);
var generator = readerMethod.GetILGenerator();
generator.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);
generator.Emit(System.Reflection.Emit.OpCodes.Castclass, this.Type);
generator.Emit(System.Reflection.Emit.OpCodes.Call, getValueMethod);
generator.Emit(System.Reflection.Emit.OpCodes.Ret);
resourceContainer.ReadFromContextDelegate = (Func)readerMethod.CreateDelegate(typeof(Func));
}
Debug.Assert(resourceContainer.ReadFromContextDelegate != null, "resourceContainer.ReadFromContextDelegate != null");
return resourceContainer.ReadFromContextDelegate(this.CurrentDataSource);
}
///
/// Populate types for metadata specified by the provider
///
/// list of types specified by the provider
/// list of already known types
/// list of already known types and their immediate children
/// list of entity sets as specified in the data source type
protected override void PopulateMetadataForUserSpecifiedTypes(
IEnumerable userSpecifiedTypes,
IDictionary knownTypes,
IDictionary> childTypes,
IEnumerable entitySets)
{
Queue unvisitedTypes = new Queue();
foreach (Type type in userSpecifiedTypes)
{
ResourceType resourceType;
if (TryGetType(knownTypes, type, out resourceType))
{
continue;
}
if (IsEntityOrComplexType(type, knownTypes, childTypes, unvisitedTypes) == null)
{
throw new InvalidOperationException(Strings.BadProvider_InvalidTypeSpecified(type.FullName));
}
}
PopulateMetadataForTypes(knownTypes, childTypes, unvisitedTypes, entitySets);
}
///
/// Populate metadata for the given clr type.
///
/// type whose metadata needs to be loaded.
/// list of already known resource types.
/// list of already known types and their immediate children
/// list of entity sets as specified in the data source.
/// resource type containing metadata for the given clr type.
protected override ResourceType PopulateMetadataForType(
Type type,
IDictionary knownTypes,
IDictionary> childTypes,
IEnumerable entitySets)
{
Queue unvisitedTypes = new Queue();
ResourceType resourceType;
if (!TryGetType(knownTypes, type, out resourceType))
{
resourceType = IsEntityOrComplexType(type, knownTypes, childTypes, unvisitedTypes);
if (resourceType != null)
{
PopulateMetadataForTypes(knownTypes, childTypes, unvisitedTypes, entitySets);
}
}
return resourceType;
}
/// Checks whether the specified type is a complex type.
/// Type to check.
///
/// true if the specified type is a complex type; false otherwise. Note
/// that resources are not distinguished from complex types.
///
private static bool IsComplexType(Type type)
{
Debug.Assert(type != null, "type != null");
// Complex types are all types that contain public properties of primitive
// types.
//
// We purposefully ignore certain known classes which fit this description
// but we know are not meaningful for Astoria:
// - System.Array: what would get serialized would be Length, IsFixed, etc.
// - Pointers: we would otherwise serialize the pointer size
// - COM object wrappers
// - interface: since we will never know what the exact type of the instance will be.
if (!type.IsVisible || type.IsArray || type.IsPointer || type.IsCOMObject || type.IsInterface ||
type == typeof(IntPtr) || type == typeof(UIntPtr) || type == typeof(char) ||
type == typeof(TimeSpan) || type == typeof(DateTimeOffset) || type == typeof(Uri) ||
type.IsEnum)
{
return false;
}
return true;
}
///
/// Checks whether there is a key defined for the given type.
///
/// type to check
///
/// Whether is being considered as a possible
/// entity type.
///
/// returns true if there are one or key properties present else returns false
private static bool DoesTypeHaveKeyProperties(Type type, bool entityTypeCandidate)
{
Debug.Assert(type != null, "type != null");
// Check for properties declared on this element only
foreach (PropertyInfo property in type.GetProperties(WebUtil.PublicInstanceBindingFlags | BindingFlags.DeclaredOnly))
{
ResourceKeyKind keyKind;
if (IsPropertyKeyProperty(property, out keyKind))
{
if (keyKind == ResourceKeyKind.AttributedKey && !entityTypeCandidate)
{
throw new InvalidOperationException(Strings.ReflectionProvider_EntityTypeHasKeyButNoEntitySet(type.FullName));
}
if (!entityTypeCandidate)
{
return false;
}
return true;
}
}
return false;
}
///
/// Populates the metadata for the given unvisited types and all the associated types with this type
///
/// list of known types
/// list of already known types and their immediate children
/// list of unvisited type
/// Available entity sets.
private static void PopulateMetadataForTypes(
IDictionary knownTypes,
IDictionary> childTypes,
Queue unvisitedTypes,
IEnumerable entitySets)
{
Debug.Assert(knownTypes != null, "knownTypes != null");
Debug.Assert(unvisitedTypes != null, "unvisitedTypes != null");
Debug.Assert(entitySets != null, "entitySets != null");
// Start walking down all the types
while (unvisitedTypes.Count != 0)
{
// get the unvisited element
ResourceType type = unvisitedTypes.Dequeue();
// Go through all the properties and find out one or more complex types
BuildTypeProperties(type, knownTypes, childTypes, unvisitedTypes, entitySets);
}
}
///
/// Walks through the list of ancestors and finds the root base type and collects metadata for the entire chain of ancestors
///
/// type whose ancestors metadata needs to be populated
/// list of already known types
/// list of already known types and their immediate children
/// list of unvisited types
/// Whether is a candidate to be an entity type.
/// return true if this given type is a entity type, otherwise returns false
private static ResourceType BuildHierarchyForEntityType(
Type type,
IDictionary knownTypes,
IDictionary> childTypes,
Queue unvisitedTypes,
bool entityTypeCandidate)
{
List ancestors = new List();
if (!type.IsVisible)
{
return null;
}
if (CommonUtil.IsUnsupportedType(type))
{
// deriving from an unsupported type is not allowed
throw new InvalidOperationException(Strings.BadProvider_UnsupportedType(type.FullName));
}
Type baseType = type;
ResourceType baseResourceType = null;
// Since this method is also used on property types, which can be interfaces,
// Base types can be null
while (baseType != null)
{
// Try and check if the base type is already loaded
if (TryGetType(knownTypes, baseType, out baseResourceType))
{
break;
}
ancestors.Add(baseType);
baseType = baseType.BaseType;
}
if (baseResourceType == null)
{
// If entityTypeCandidate is false, then it means that the current type can't
// be a entity type with keys. In other words, it must derive from an existing
// type. Otherwise, its not an entity type
if (entityTypeCandidate == false)
{
return null;
}
// Find the last ancestor which has key defined
for (int i = ancestors.Count - 1; i >= 0; i--)
{
if (CommonUtil.IsUnsupportedType(ancestors[i]))
{
// deriving from an unsupported type is not allowed
throw new InvalidOperationException(Strings.BadProvider_UnsupportedAncestorType(type.FullName, ancestors[i].FullName));
}
if (DoesTypeHaveKeyProperties(ancestors[i], entityTypeCandidate))
{
break;
}
// Else this type is not interesting. Remove it from the ancestors list
ancestors.RemoveAt(i);
}
}
else if (baseResourceType.ResourceTypeKind != ResourceTypeKind.EntityType)
{
return null;
}
else if (ancestors.Count == 0)
{
// we might have found the top level element.So just return
return baseResourceType;
}
// For all the valid ancestors, add the type to the list of types encountered
// and unvisited types
// its important that we enqueue the ancestors first, since when we populate member metadata
// we can make sure that the base type is fully populated
for (int i = ancestors.Count - 1; i >= 0; i--)
{
ResourceType entityType = ReflectionServiceProvider.CreateResourceType(ancestors[i], ResourceTypeKind.EntityType, baseResourceType, knownTypes, childTypes);
unvisitedTypes.Enqueue(entityType);
baseResourceType = entityType;
}
return baseResourceType;
}
///
/// Populates the metadata for the properties of the given resource type
///
/// resource type whose properties metadata needs to be populated
/// list of known types
/// list of already known types and their immediate children
/// list of unvisited type
/// Available entity sets.
private static void BuildTypeProperties(
ResourceType parentResourceType,
IDictionary knownTypes,
IDictionary> childTypes,
Queue unvisitedTypes,
IEnumerable entitySets)
{
Debug.Assert(parentResourceType != null, "parentResourceType != null");
Debug.Assert(knownTypes != null, "knownTypes != null");
Debug.Assert(unvisitedTypes != null, "unvisitedTypes != null");
Debug.Assert(entitySets != null, "entitySets != null");
BindingFlags bindingFlags = WebUtil.PublicInstanceBindingFlags;
// For non root types, we should only look for properties that are declared for this type
if (parentResourceType.BaseType != null)
{
bindingFlags = bindingFlags | BindingFlags.DeclaredOnly;
}
HashSet propertiesToBeIgnored = new HashSet(IgnorePropertiesAttribute.GetProperties(parentResourceType.InstanceType, false /*inherit*/, bindingFlags), StringComparer.Ordinal);
Debug.Assert(parentResourceType.IsOpenType == false, "ReflectionServiceProvider does not support Open types.");
HashSet etagPropertyNames = new HashSet(LoadETagProperties(parentResourceType), StringComparer.Ordinal);
ResourceKeyKind keyKind = (ResourceKeyKind)Int32.MaxValue;
PropertyInfo[] properties = parentResourceType.InstanceType.GetProperties(bindingFlags);
foreach (PropertyInfo property in properties)
{
// Ignore the properties which are specified in the IgnoreProperties attribute
if (propertiesToBeIgnored.Contains(property.Name))
{
continue;
}
if (property.CanRead && property.GetIndexParameters().Length == 0)
{
ResourcePropertyKind kind = (ResourcePropertyKind)(-1);
ResourceKeyKind currentKeyKind = (ResourceKeyKind)(-1);
ResourceType resourceType;
Type resourcePropertyType = property.PropertyType;
ResourceSet container = null;
bool collection = false;
if (!TryGetType(knownTypes, resourcePropertyType, out resourceType))
{
Type collectionType = GetIEnumerableElement(property.PropertyType);
if (collectionType != null)
{
TryGetType(knownTypes, collectionType, out resourceType);
// Even if the above method returns false, we should set the
// following variable appropriately, so that we can use them below
collection = true;
resourcePropertyType = collectionType;
}
}
if (resourceType != null)
{
#region Already Known Type
if (resourceType.ResourceTypeKind == ResourceTypeKind.Primitive)
{
// Check for key property only on root types, since keys must be defined on the root types
if (parentResourceType.BaseType == null && parentResourceType.ResourceTypeKind == ResourceTypeKind.EntityType && IsPropertyKeyProperty(property, out currentKeyKind))
{
if ((int)currentKeyKind < (int)keyKind)
{
if (parentResourceType.KeyProperties.Count != 0)
{
// Remove the existing property as key property - mark it as non key property
parentResourceType.RemoveKeyProperties();
}
keyKind = currentKeyKind;
kind = ResourcePropertyKind.Key | ResourcePropertyKind.Primitive;
}
else if ((int)currentKeyKind == (int)keyKind)
{
Debug.Assert(currentKeyKind == ResourceKeyKind.AttributedKey, "This is the only way of specifying composite keys");
kind = ResourcePropertyKind.Key | ResourcePropertyKind.Primitive;
}
else
{
kind = ResourcePropertyKind.Primitive;
}
}
else
{
kind = ResourcePropertyKind.Primitive;
}
}
else if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType)
{
kind = ResourcePropertyKind.ComplexType;
}
else if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
kind = collection ? ResourcePropertyKind.ResourceSetReference : ResourcePropertyKind.ResourceReference;
}
#endregion // Already Known Type
}
else
{
resourceType = IsEntityOrComplexType(resourcePropertyType, knownTypes, childTypes, unvisitedTypes);
if (resourceType != null)
{
if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType)
{
kind = ResourcePropertyKind.ComplexType;
}
else
{
Debug.Assert(resourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "Must be an entity type");
kind = collection ? ResourcePropertyKind.ResourceSetReference : ResourcePropertyKind.ResourceReference;
}
}
}
// if resource type is null OR
// if resource type is a collection of primitive or complex types OR
// if complex type has a property of entity type
if (resourceType == null ||
(resourceType.ResourceTypeKind != ResourceTypeKind.EntityType && collection) ||
(resourceType.ResourceTypeKind == ResourceTypeKind.EntityType && parentResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType))
{
if (resourceType == null)
{
if (CommonUtil.IsUnsupportedType(resourcePropertyType))
{
throw new InvalidOperationException(Strings.BadProvider_UnsupportedPropertyType(property.Name, parentResourceType.FullName));
}
throw new InvalidOperationException(Strings.ReflectionProvider_InvalidProperty(property.Name, parentResourceType.FullName));
}
else
{
// collection of complex types not supported
throw new InvalidOperationException(Strings.ReflectionProvider_CollectionOfPrimitiveOrComplexNotSupported(property.Name, parentResourceType.FullName));
}
}
if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
container = InternalGetContainerForResourceType(resourcePropertyType, entitySets);
if (container == null)
{
throw new InvalidOperationException(Strings.ReflectionProvider_EntityPropertyWithNoEntitySet(parentResourceType.FullName, property.Name));
}
}
if (etagPropertyNames.Remove(property.Name))
{
kind |= ResourcePropertyKind.ETag;
}
ResourceProperty resourceProperty = new ResourceProperty(property.Name, kind, resourceType);
MimeTypeAttribute attribute = MimeTypeAttribute.GetMimeTypeAttribute(property);
if (attribute != null)
{
resourceProperty.MimeType = attribute.MimeType;
}
parentResourceType.AddProperty(resourceProperty);
}
else
{
throw new InvalidOperationException(Strings.ReflectionProvider_InvalidProperty(property.Name, parentResourceType.FullName));
}
}
if (parentResourceType.ResourceTypeKind == ResourceTypeKind.EntityType &&
(parentResourceType.KeyProperties == null || parentResourceType.KeyProperties.Count == 0))
{
throw new InvalidOperationException(Strings.ReflectionProvider_KeyPropertiesCannotBeIgnored(parentResourceType.FullName));
}
if (etagPropertyNames.Count != 0)
{
throw new InvalidOperationException(Strings.ReflectionProvider_ETagPropertyNameNotValid(etagPropertyNames.ElementAt(0), parentResourceType.FullName));
}
}
///
/// If the given type is a entity or complex type, it returns the resource type corresponding to the given type
///
/// clr type
/// list of already known types
/// list of already known types and their immediate children
/// list of unvisited types
/// resource type corresponding to the given clr type, if the clr type is entity or complex
private static ResourceType IsEntityOrComplexType(
Type type,
IDictionary knownTypes,
IDictionary> childTypes,
Queue unvisitedTypes)
{
// Ignore values types here. We do not support resources of values type (entity or complex)
if (type.IsValueType || CommonUtil.IsUnsupportedType(type))
{
return null;
}
ResourceType resourceType = BuildHierarchyForEntityType(type, knownTypes, childTypes, unvisitedTypes, false /* entityTypeCandidate */);
if (resourceType == null && IsComplexType(type))
{
resourceType = ReflectionServiceProvider.CreateResourceType(type, ResourceTypeKind.ComplexType, null, knownTypes, childTypes);
unvisitedTypes.Enqueue(resourceType);
}
return resourceType;
}
/// Get the resource set for the given clr type.
/// clr type for which resource set name needs to be returned
/// Available entity sets to consider.
/// The container for its type, null if not found.
private static ResourceSet InternalGetContainerForResourceType(Type type, IEnumerable entitySets)
{
Debug.Assert(type != null, "type != null");
Debug.Assert(entitySets != null, "entitySets != null");
// For each entity set, find out which one matches the type of this resource
foreach (ResourceSet entitySetInfo in entitySets)
{
if (entitySetInfo.ResourceType.InstanceType.IsAssignableFrom(type))
{
return entitySetInfo;
}
}
return null;
}
///
/// Find out all the derived types in the list of assemblies and then populate metadata for those types
///
/// list of known types
/// list of already known types and their immediate children
/// list of unvisited types
/// Available entity sets.
private static void PopulateMetadataForDerivedTypes(
IDictionary knownTypes,
IDictionary> childTypes,
Queue unvisitedTypes,
IEnumerable entitySets)
{
Debug.Assert(knownTypes != null, "knownTypes != null");
Debug.Assert(unvisitedTypes != null, "unvisitedTypes != null");
Debug.Assert(entitySets != null, "entitySets != null");
// Find all the root resource entity types
List rootTypes = new List();
foreach (ResourceType resourceType in knownTypes.Values)
{
if (resourceType.BaseType == null &&
resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
rootTypes.Add(resourceType);
}
}
// Use the default comparer, which calls Assembly.Equals (not a simple reference comparison).
HashSet assemblies = new HashSet(EqualityComparer.Default);
List derivedTypes = new List();
// Walk through all the types in the assemblies and find all the derived types
foreach (ResourceType resourceType in knownTypes.Values)
{
// No need to look into primitive types, as these live in system assemblies.
if (resourceType.ResourceTypeKind == ResourceTypeKind.Primitive)
{
continue;
}
Assembly assembly = resourceType.InstanceType.Assembly;
//// ignore if the assembly has already been scanned
if (assemblies.Contains(assembly))
{
continue;
}
// Walk all the types in that assembly
foreach (Type type in assembly.GetTypes())
{
// skip all the non visible types or types which have generic parameters
if (!type.IsVisible || HasGenericParameters(type))
{
continue;
}
// Skip the type if its already loaded
if (knownTypes.ContainsKey(type))
{
continue;
}
// Check if this type dervies from any one of the root types
for (int i = 0; i < rootTypes.Count; i++)
{
if (rootTypes[i].InstanceType.IsAssignableFrom(type))
{
derivedTypes.Add(type);
}
}
}
assemblies.Add(assembly);
}
foreach (Type type in derivedTypes)
{
BuildHierarchyForEntityType(type, knownTypes, childTypes, unvisitedTypes, false /* entityTypeCandidate */);
PopulateMetadataForTypes(knownTypes, childTypes, unvisitedTypes, entitySets);
}
}
///
/// Loads the etag properties for the given resource type
///
/// resource type whose etag property names need to be loaded.
/// the list of properties that form the etag for the given resource type.
private static IEnumerable LoadETagProperties(ResourceType resourceType)
{
// if it is the root type, then we need to inherit the attribute from the base type.
// otherwise, we need not, since if the base type already has it, the appropriate properties
// must already have been marked as concurrency properties.
bool inherit = resourceType.BaseType == null;
// Read the etag attribute from the type and return it
ETagAttribute[] attributes = (ETagAttribute[])resourceType.InstanceType.GetCustomAttributes(typeof(ETagAttribute), inherit);
Debug.Assert(attributes.Length <= 1, "Only one attribute can be specified per type");
if (attributes.Length == 1)
{
// Validate the property names
// we may need to cache them instead of reading them everytime
return attributes[0].PropertyNames;
}
return WebUtil.EmptyStringArray;
}
///
/// returns the new resource type instance
///
/// backing clr type for the resource.
/// kind of the resource.
/// base type of the resource.
/// list of already known types
/// list of already known types and their immediate children
/// returns a new instance of the resource type containing all the metadata.
private static ResourceType CreateResourceType(
Type type,
ResourceTypeKind kind,
ResourceType baseType,
IDictionary knownTypes,
IDictionary> childTypes)
{
ResourceType resourceType = new ResourceType(type, kind, baseType, type.Namespace, GetModelTypeName(type), type.IsAbstract);
resourceType.IsOpenType = false;
// We need to look at inherited attributes as well so we pass true for inherit argument.
if (type.GetCustomAttributes(typeof(HasStreamAttribute), true /* inherit */).Length == 1)
{
resourceType.IsMediaLinkEntry = true;
}
knownTypes.Add(type, resourceType);
childTypes.Add(resourceType, null);
if (baseType != null)
{
Debug.Assert(childTypes.ContainsKey(baseType), "childTypes.ContainsKey(baseType)");
if (childTypes[baseType] == null)
{
childTypes[baseType] = new List();
}
childTypes[baseType].Add(resourceType);
}
return resourceType;
}
///
/// Gets the type name (without namespace) of the specified ,
/// appropriate as an externally-visible type name.
///
/// Type to get name for.
/// The type name for .
private static string GetModelTypeName(Type type)
{
Debug.Assert(type != null, "type != null");
if (type.IsGenericType)
{
Type[] genericArguments = type.GetGenericArguments();
StringBuilder builder = new StringBuilder(type.Name.Length * 2 * (1 + genericArguments.Length));
if (type.IsNested)
{
Debug.Assert(type.DeclaringType != null, "type.DeclaringType != null");
builder.Append(GetModelTypeName(type.DeclaringType));
builder.Append('_');
}
builder.Append(type.Name);
builder.Append('[');
for (int i = 0; i < genericArguments.Length; i++)
{
if (i > 0)
{
builder.Append(' ');
}
string genericNamespace = WebUtil.GetModelTypeNamespace(genericArguments[i]);
if (!String.IsNullOrEmpty(genericNamespace))
{
builder.Append(genericNamespace);
builder.Append('.');
}
builder.Append(GetModelTypeName(genericArguments[i]));
}
builder.Append(']');
return builder.ToString();
}
else if (type.IsNested)
{
Debug.Assert(type.DeclaringType != null, "type.DeclaringType != null");
return GetModelTypeName(type.DeclaringType) + "_" + type.Name;
}
else
{
return type.Name;
}
}
///
/// Checks whether the given type is a generic type with a generic parameter.
///
/// type which needs to be checked.
/// Returns true, if the is generic and has generic parameters. Otherwise returns false.
private static bool HasGenericParameters(Type type)
{
if (type.IsGenericType)
{
foreach (Type arg in type.GetGenericArguments())
{
if (arg.IsGenericParameter)
{
return true;
}
}
}
return false;
}
///
/// Returns true if the subtree of expansions rooted in the specified
/// contains either a filter or paging/maxresult constraint.
///
/// The root of the expansions tree to inspect.
/// True if BasicExpandProvider should be used to process a query with this tree
/// or false otherwise.
private static bool ShouldUseBasicExpandProvider(ExpandedProjectionNode expandedNode)
{
foreach (ProjectionNode node in expandedNode.Nodes)
{
ExpandedProjectionNode childExpandedNode = node as ExpandedProjectionNode;
if (childExpandedNode != null)
{
if (childExpandedNode.HasFilterOrMaxResults)
{
return true;
}
if (ShouldUseBasicExpandProvider(childExpandedNode))
{
return true;
}
}
}
return false;
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.