//---------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
namespace System.Data.Metadata.Edm
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
///
/// The validation severity level
///
internal enum ValidationSeverity
{
///
/// Warning
///
Warning,
///
/// Error
///
Error,
///
/// Internal
///
Internal
}
///
/// Class representing a validtion error event args
///
internal class ValidationErrorEventArgs : EventArgs
{
private EdmItemError _validationError;
///
/// Construct the validation error event args with a validation error object
///
/// The validation error object for this event args
public ValidationErrorEventArgs(EdmItemError validationError)
{
_validationError = validationError;
}
///
/// Gets the validation error object this event args
///
public EdmItemError ValidationError
{
get
{
return _validationError;
}
}
}
///
/// Class for representing the validator
///
internal class EdmValidator
{
private bool _skipReadOnlyItems;
///
/// Gets or Sets whether the validator should skip readonly items
///
internal bool SkipReadOnlyItems
{
get
{
return _skipReadOnlyItems;
}
set
{
_skipReadOnlyItems = value;
}
}
///
/// Validate a collection of items in a batch
///
/// A collection of items to validate
/// List of validation errors that were previously collected by the caller. if it encounters
/// more errors, it adds them to this list of errors
public void Validate(IEnumerable items, List ospaceErrors)
where T : EdmType // O-Space only supports EdmType
{
EntityUtil.CheckArgumentNull(items, "items");
EntityUtil.CheckArgumentNull(items, "ospaceErrors");
HashSet validatedItems = new HashSet();
foreach (MetadataItem item in items)
{
// Just call the internal helper method for each item
InternalValidate(item, ospaceErrors, validatedItems);
}
}
///
/// Event hook to perform preprocessing on the validation error before it gets added to a list of errors
///
/// The event args for this event
protected virtual void OnValidationError(ValidationErrorEventArgs e)
{
}
///
/// Invoke the event hook Add an error to the list
///
/// The list of errors to add to
/// The new error to add
private void AddError(List 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);
}
///
/// Allows derived classes to perform additional validation
///
/// The item to perform additional validation
/// A collection of errors
protected virtual IEnumerable CustomValidate(MetadataItem item)
{
return null;
}
///
/// Validate an item object
///
/// The item to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void InternalValidate(MetadataItem item, List errors, HashSet 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 customErrors = CustomValidate(item);
if (customErrors != null)
{
errors.AddRange(customErrors);
}
}
///
/// Validate an CollectionType object
///
/// The CollectionType object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateCollectionType(CollectionType item, List errors, HashSet 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);
}
}
///
/// Validate an ComplexType object
///
/// The ComplexType object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateComplexType(ComplexType item, List errors, HashSet validatedItems)
{
ValidateStructuralType(item, errors, validatedItems);
}
///
/// Validate an EdmType object
///
/// The EdmType object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateEdmType(EdmType item, List errors, HashSet 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);
}
}
///
/// Validate an EntityType object
///
/// The EntityType object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateEntityType(EntityType item, List errors, HashSet 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);
}
///
/// Validate an Facet object
///
/// The Facet object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateFacet(Facet item, List errors, HashSet 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);
}
}
///
/// Validate an MetadataItem object
///
/// The MetadataItem object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateItem(MetadataItem item, List errors, HashSet 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);
}
}
}
///
/// Validate an EdmMember object
///
/// The item object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateEdmMember(EdmMember item, List errors, HashSet 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);
}
}
///
/// Validate an MetadataProperty object
///
/// The MetadataProperty object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateMetadataProperty(MetadataProperty item, List errors, HashSet 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);
}
}
}
///
/// Validate an NavigationProperty object
///
/// The NavigationProperty object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateNavigationProperty(NavigationProperty item, List errors, HashSet 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);
}
///
/// Validate an GetPrimitiveType object
///
/// The GetPrimitiveType object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidatePrimitiveType(PrimitiveType item, List errors, HashSet validatedItems)
{
ValidateSimpleType(item, errors, validatedItems);
}
///
/// Validate an EdmProperty object
///
/// The EdmProperty object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateEdmProperty(EdmProperty item, List errors, HashSet validatedItems)
{
ValidateEdmMember(item, errors, validatedItems);
}
///
/// Validate an RefType object
///
/// The RefType object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateRefType(RefType item, List errors, HashSet 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);
}
}
///
/// Validate an SimpleType object
///
/// The SimpleType object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateSimpleType(SimpleType item, List errors, HashSet validatedItems)
{
ValidateEdmType(item, errors, validatedItems);
}
///
/// Validate an StructuralType object
///
/// The StructuralType object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateStructuralType(StructuralType item, List errors, HashSet validatedItems)
{
ValidateEdmType(item, errors, validatedItems);
// Just validate each member, the collection already guaranteed that there aren't any nulls in the collection
Dictionary allMembers = new Dictionary();
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);
}
}
///
/// Validate an TypeUsage object
///
/// The TypeUsage object to validate
/// An error collection for adding validation errors
/// A dictionary keeping track of items that have been validated
private void ValidateTypeUsage(TypeUsage item, List errors, HashSet 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);
}
}
}
}