//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- 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); } } } }