|
//---------------------------------------------------------------------
// <copyright file="EdmValidator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
namespace System.Data.Metadata.Edm
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
/// <summary>
/// The validation severity level
/// </summary>
internal enum ValidationSeverity
{
/// <summary>
/// Warning
/// </summary>
Warning,
/// <summary>
/// Error
/// </summary>
Error,
/// <summary>
/// Internal
/// </summary>
Internal
}
/// <summary>
/// Class representing a validtion error event args
/// </summary>
internal class ValidationErrorEventArgs : EventArgs
{
private EdmItemError _validationError;
/// <summary>
/// Construct the validation error event args with a validation error object
/// </summary>
/// <param name="validationError">The validation error object for this event args</param>
public ValidationErrorEventArgs(EdmItemError validationError)
{
_validationError = validationError;
}
/// <summary>
/// Gets the validation error object this event args
/// </summary>
public EdmItemError ValidationError
{
get
{
return _validationError;
}
}
}
/// <summary>
/// Class for representing the validator
/// </summary>
internal class EdmValidator
{
private bool _skipReadOnlyItems;
/// <summary>
/// Gets or Sets whether the validator should skip readonly items
/// </summary>
internal bool SkipReadOnlyItems
{
get
{
return _skipReadOnlyItems;
}
set
{
_skipReadOnlyItems = value;
}
}
/// <summary>
/// Validate a collection of items in a batch
/// </summary>
/// <param name="items">A collection of items to validate</param>
/// <param name="ospaceErrors">List of validation errors that were previously collected by the caller. if it encounters
/// more errors, it adds them to this list of errors</param>
public void Validate<T>(IEnumerable<T> items, List<EdmItemError> ospaceErrors)
where T : EdmType // O-Space only supports EdmType
{
EntityUtil.CheckArgumentNull(items, "items");
EntityUtil.CheckArgumentNull(items, "ospaceErrors");
HashSet<MetadataItem> validatedItems = new HashSet<MetadataItem>();
foreach (MetadataItem item in items)
{
// Just call the internal helper method for each item
InternalValidate(item, ospaceErrors, validatedItems);
}
}
/// <summary>
/// Event hook to perform preprocessing on the validation error before it gets added to a list of errors
/// </summary>
/// <param name="e">The event args for this event</param>
protected virtual void OnValidationError(ValidationErrorEventArgs e)
{
}
/// <summary>
/// Invoke the event hook Add an error to the list
/// </summary>
/// <param name="errors">The list of errors to add to</param>
/// <param name="newError">The new error to add</param>
private void AddError(List<EdmItemError> errors, EdmItemError newError)
{
// Create an event args object and call the event hook, the derived class may have changed
// the validation error to some other object, in which case we add the validation error object
// coming from the event args
ValidationErrorEventArgs e = new ValidationErrorEventArgs(newError);
OnValidationError(e);
errors.Add(e.ValidationError);
}
/// <summary>
/// Allows derived classes to perform additional validation
/// </summary>
/// <param name="item">The item to perform additional validation</param>
/// <returns>A collection of errors</returns>
protected virtual IEnumerable<EdmItemError> CustomValidate(MetadataItem item)
{
return null;
}
/// <summary>
/// Validate an item object
/// </summary>
/// <param name="item">The item to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void InternalValidate(MetadataItem item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
Debug.Assert(item != null, "InternalValidate is called with a null item, the caller should check for null first");
// If the item has already been validated or we need to skip readonly items, then skip
if ( (item.IsReadOnly && SkipReadOnlyItems) || validatedItems.Contains(item) )
{
return;
}
// Add this item to the dictionary so we won't validate this again. Note that we only do this
// in this function because every other function should eventually delegate to here
validatedItems.Add(item);
// Check to make sure the item has an identity
if (string.IsNullOrEmpty(item.Identity))
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_EmptyIdentity,item));
}
switch (item.BuiltInTypeKind)
{
case BuiltInTypeKind.CollectionType:
ValidateCollectionType((CollectionType)item, errors, validatedItems);
break;
case BuiltInTypeKind.ComplexType:
ValidateComplexType((ComplexType)item, errors, validatedItems);
break;
case BuiltInTypeKind.EntityType:
ValidateEntityType((EntityType)item, errors, validatedItems);
break;
case BuiltInTypeKind.Facet:
ValidateFacet((Facet)item, errors, validatedItems);
break;
case BuiltInTypeKind.MetadataProperty:
ValidateMetadataProperty((MetadataProperty)item, errors, validatedItems);
break;
case BuiltInTypeKind.NavigationProperty:
ValidateNavigationProperty((NavigationProperty)item, errors, validatedItems);
break;
case BuiltInTypeKind.PrimitiveType:
ValidatePrimitiveType((PrimitiveType)item, errors, validatedItems);
break;
case BuiltInTypeKind.EdmProperty:
ValidateEdmProperty((EdmProperty)item, errors, validatedItems);
break;
case BuiltInTypeKind.RefType:
ValidateRefType((RefType)item, errors, validatedItems);
break;
case BuiltInTypeKind.TypeUsage:
ValidateTypeUsage((TypeUsage)item, errors, validatedItems);
break;
// Abstract classes
case BuiltInTypeKind.EntityTypeBase:
case BuiltInTypeKind.EdmType:
case BuiltInTypeKind.MetadataItem:
case BuiltInTypeKind.EdmMember:
case BuiltInTypeKind.RelationshipEndMember:
case BuiltInTypeKind.RelationshipType:
case BuiltInTypeKind.SimpleType:
case BuiltInTypeKind.StructuralType:
Debug.Assert(false, "An instance with a built in type kind refering to the abstract type " + item.BuiltInTypeKind + " is encountered");
break;
default:
//Debug.Assert(false, String.Format(CultureInfo.InvariantCulture, "Validate not implemented for {0}", item.BuiltInTypeKind));
break;
}
// Performs other custom validation
IEnumerable<EdmItemError> customErrors = CustomValidate(item);
if (customErrors != null)
{
errors.AddRange(customErrors);
}
}
/// <summary>
/// Validate an CollectionType object
/// </summary>
/// <param name="item">The CollectionType object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateCollectionType(CollectionType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
ValidateEdmType(item, errors, validatedItems);
// Check that it doesn't have a base type
if (item.BaseType != null)
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_CollectionTypesCannotHaveBaseType, item));
}
if (item.TypeUsage == null)
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_CollectionHasNoTypeUsage, item));
}
else
{
// Just validate the element type, there is nothing on the collection itself to validate
InternalValidate(item.TypeUsage, errors, validatedItems);
}
}
/// <summary>
/// Validate an ComplexType object
/// </summary>
/// <param name="item">The ComplexType object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateComplexType(ComplexType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
ValidateStructuralType(item, errors, validatedItems);
}
/// <summary>
/// Validate an EdmType object
/// </summary>
/// <param name="item">The EdmType object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateEdmType(EdmType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
ValidateItem(item, errors, validatedItems);
// Check that this type has a name and namespace
if (string.IsNullOrEmpty(item.Name))
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_TypeHasNoName, item));
}
if (null == item.NamespaceName ||
item.DataSpace != DataSpace.OSpace && string.Empty == item.NamespaceName)
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_TypeHasNoNamespace, item));
}
// We don't need to verify that the base type chain eventually gets to null because
// the CLR doesn't allow loops in class hierarchies.
if (item.BaseType != null)
{
// Validate the base type
InternalValidate(item.BaseType, errors, validatedItems);
}
}
/// <summary>
/// Validate an EntityType object
/// </summary>
/// <param name="item">The EntityType object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateEntityType(EntityType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
// check the base EntityType has Keys
if (item.BaseType == null)
{
// Check that there is at least one key member
if (item.KeyMembers.Count < 1)
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_NoKeyMembers(item.FullName), item));
}
else
{
foreach (EdmProperty keyProperty in item.KeyMembers)
{
if (keyProperty.Nullable)
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_NullableEntityKeyProperty(keyProperty.Name, item.FullName), keyProperty));
}
}
}
}
// Continue to process the entity to see if there are other errors. This allows the user to
// fix as much as possible at the same time.
ValidateStructuralType(item, errors, validatedItems);
}
/// <summary>
/// Validate an Facet object
/// </summary>
/// <param name="item">The Facet object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateFacet(Facet item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
ValidateItem(item, errors, validatedItems);
// Check that this facet has a name
if (string.IsNullOrEmpty(item.Name))
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_FacetHasNoName, item));
}
// Validate the type
if (item.FacetType == null)
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_FacetTypeIsNull, item));
}
else
{
InternalValidate(item.FacetType, errors, validatedItems);
}
}
/// <summary>
/// Validate an MetadataItem object
/// </summary>
/// <param name="item">The MetadataItem object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateItem(MetadataItem item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
// In here, we look at RawMetadataProperties because it dynamically add MetadataProperties when you access the
// normal MetadataProperties property. This avoids needless validation and infinite recursion
if (item.RawMetadataProperties != null)
{
foreach (MetadataProperty itemAttribute in item.MetadataProperties)
{
InternalValidate(itemAttribute, errors, validatedItems);
}
}
}
/// <summary>
/// Validate an EdmMember object
/// </summary>
/// <param name="item">The item object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateEdmMember(EdmMember item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
ValidateItem(item, errors, validatedItems);
// Check that this member has a name
if (string.IsNullOrEmpty(item.Name))
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MemberHasNoName, item));
}
if (item.DeclaringType == null)
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MemberHasNullDeclaringType, item));
}
else
{
InternalValidate(item.DeclaringType, errors, validatedItems);
}
if (item.TypeUsage == null)
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MemberHasNullTypeUsage, item));
}
else
{
InternalValidate(item.TypeUsage, errors, validatedItems);
}
}
/// <summary>
/// Validate an MetadataProperty object
/// </summary>
/// <param name="item">The MetadataProperty object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateMetadataProperty(MetadataProperty item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
// Validate only for user added item attributes, for system attributes, we can skip validation
if (item.PropertyKind == PropertyKind.Extended)
{
ValidateItem(item, errors, validatedItems);
// Check that this member has a name
if (string.IsNullOrEmpty(item.Name))
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MetadataPropertyHasNoName, item));
}
if (item.TypeUsage == null)
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_ItemAttributeHasNullTypeUsage, item));
}
else
{
InternalValidate(item.TypeUsage, errors, validatedItems);
}
}
}
/// <summary>
/// Validate an NavigationProperty object
/// </summary>
/// <param name="item">The NavigationProperty object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateNavigationProperty(NavigationProperty item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
// Continue to process the property to see if there are other errors. This allows the user to fix as much as possible at the same time.
ValidateEdmMember(item, errors, validatedItems);
}
/// <summary>
/// Validate an GetPrimitiveType object
/// </summary>
/// <param name="item">The GetPrimitiveType object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidatePrimitiveType(PrimitiveType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
ValidateSimpleType(item, errors, validatedItems);
}
/// <summary>
/// Validate an EdmProperty object
/// </summary>
/// <param name="item">The EdmProperty object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateEdmProperty(EdmProperty item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
ValidateEdmMember(item, errors, validatedItems);
}
/// <summary>
/// Validate an RefType object
/// </summary>
/// <param name="item">The RefType object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateRefType(RefType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
ValidateEdmType(item, errors, validatedItems);
// Check that it doesn't have a base type
if (item.BaseType != null)
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_RefTypesCannotHaveBaseType, item));
}
// Just validate the element type, there is nothing on the collection itself to validate
if (item.ElementType == null)
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_RefTypeHasNullEntityType, null));
}
else
{
InternalValidate(item.ElementType, errors, validatedItems);
}
}
/// <summary>
/// Validate an SimpleType object
/// </summary>
/// <param name="item">The SimpleType object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateSimpleType(SimpleType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
ValidateEdmType(item, errors, validatedItems);
}
/// <summary>
/// Validate an StructuralType object
/// </summary>
/// <param name="item">The StructuralType object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateStructuralType(StructuralType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
ValidateEdmType(item, errors, validatedItems);
// Just validate each member, the collection already guaranteed that there aren't any nulls in the collection
Dictionary<string, EdmMember> allMembers = new Dictionary<string, EdmMember>();
foreach (EdmMember member in item.Members)
{
// Check if the base type already has a member of the same name
EdmMember baseMember = null;
if (allMembers.TryGetValue(member.Name, out baseMember))
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_BaseTypeHasMemberOfSameName, item));
}
else
{
allMembers.Add(member.Name, member);
}
InternalValidate(member, errors, validatedItems);
}
}
/// <summary>
/// Validate an TypeUsage object
/// </summary>
/// <param name="item">The TypeUsage object to validate</param>
/// <param name="errors">An error collection for adding validation errors</param>
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
private void ValidateTypeUsage(TypeUsage item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
{
ValidateItem(item, errors, validatedItems);
if (item.EdmType == null)
{
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_TypeUsageHasNullEdmType, item));
}
else
{
InternalValidate(item.EdmType, errors, validatedItems);
}
foreach (Facet facet in item.Facets)
{
InternalValidate(facet, errors, validatedItems);
}
}
}
}
|