You've already forked linux-packaging-mono
							
							
		
			
				
	
	
		
			454 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			454 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| //---------------------------------------------------------------------
 | |
| // <copyright file="SchemaManager.cs" company="Microsoft">
 | |
| //      Copyright (c) Microsoft Corporation.  All rights reserved.
 | |
| // </copyright>
 | |
| //
 | |
| // @owner       Microsoft
 | |
| // @backupOwner Microsoft
 | |
| //---------------------------------------------------------------------
 | |
| 
 | |
| using System.Collections;
 | |
| using System.Collections.Generic;
 | |
| using System.Data.Common;
 | |
| using System.Data.Common.Utils;
 | |
| using System.Data.Entity;
 | |
| using System.Data.Mapping;
 | |
| using System.Data.Metadata.Edm;
 | |
| using System.Diagnostics;
 | |
| using System.Linq;
 | |
| using System.Xml;
 | |
| 
 | |
| namespace System.Data.EntityModel.SchemaObjectModel
 | |
| {
 | |
|     internal delegate void AttributeValueNotification(string token, Action<string, ErrorCode, EdmSchemaErrorSeverity> addError);
 | |
|     internal delegate DbProviderManifest ProviderManifestNeeded(Action<string, ErrorCode, EdmSchemaErrorSeverity> addError);
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Class responsible for parsing,validating a collection of schema
 | |
|     /// </summary>
 | |
|     [System.Diagnostics.DebuggerDisplay("DataModel={DataModel}")]
 | |
|     internal class SchemaManager
 | |
|     {
 | |
|         #region Instance Fields
 | |
|         // This keeps track of all the possible namespaces encountered till now. This helps in displaying the error to the
 | |
|         // user - if the particular type is not found, we can report whether the namespace was invalid or the type with the
 | |
|         // given name was not found in the given namespace. This also helps in validating the namespace in the using elements
 | |
|         private readonly HashSet<string> _namespaceLookUpTable = new HashSet<string>(StringComparer.Ordinal);
 | |
| 
 | |
|         // List of all the schema types across all the schemas. This is to ensure that there is no duplicate type encountered
 | |
|         // across schemas
 | |
|         private readonly SchemaElementLookUpTable<SchemaType> _schemaTypes = new SchemaElementLookUpTable<SchemaType>();
 | |
| 
 | |
|         // We want to stop parsing/resolving/validation after the first 100 errors
 | |
|         private const int MaxErrorCount = 100;
 | |
| 
 | |
|         // delay loaded
 | |
|         private DbProviderManifest _providerManifest;
 | |
|         private PrimitiveSchema _primitiveSchema;
 | |
|         private double effectiveSchemaVersion = XmlConstants.UndefinedVersion;
 | |
| 
 | |
|         private readonly SchemaDataModelOption _dataModel;
 | |
|         private readonly ProviderManifestNeeded _providerManifestNeeded;
 | |
|         private readonly AttributeValueNotification _providerNotification;
 | |
|         private readonly AttributeValueNotification _providerManifestTokenNotification;
 | |
|         #endregion
 | |
| 
 | |
|         #region Constructor
 | |
|         private SchemaManager(SchemaDataModelOption dataModel, AttributeValueNotification providerNotification, AttributeValueNotification providerManifestTokenNotification, ProviderManifestNeeded providerManifestNeeded)
 | |
|         {
 | |
|             _dataModel = dataModel;
 | |
|             _providerNotification = providerNotification;
 | |
|             _providerManifestTokenNotification = providerManifestTokenNotification;
 | |
|             _providerManifestNeeded = providerManifestNeeded;
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Public Methods
 | |
| 
 | |
| 
 | |
|         
 | |
|         public static IList<EdmSchemaError> LoadProviderManifest(XmlReader xmlReader, string location,
 | |
|             bool checkForSystemNamespace, out Schema schema)
 | |
|         {
 | |
|             IList<Schema> schemaCollection = new List<Schema>(1);
 | |
| 
 | |
|             DbProviderManifest providerManifest = checkForSystemNamespace ? EdmProviderManifest.Instance : null;
 | |
|             IList<EdmSchemaError> errors = ParseAndValidate(new XmlReader[] { xmlReader },
 | |
|                 new string[] { location }, SchemaDataModelOption.ProviderManifestModel,
 | |
|                 providerManifest, out schemaCollection);
 | |
| 
 | |
|             // In case of errors, there are no schema in the schema collection
 | |
|             if (schemaCollection.Count != 0)
 | |
|             {
 | |
|                 schema = schemaCollection[0];
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 Debug.Assert(errors.Count != 0, "There must be some error encountered");
 | |
|                 schema = null;
 | |
|             }
 | |
| 
 | |
|             return errors;
 | |
|         }
 | |
| 
 | |
|         public static void NoOpAttributeValueNotification(string attributeValue, Action<string, ErrorCode, EdmSchemaErrorSeverity> addError) { }
 | |
|         
 | |
|         public static IList<EdmSchemaError> ParseAndValidate(IEnumerable<XmlReader> xmlReaders,
 | |
|             IEnumerable<string> sourceFilePaths, SchemaDataModelOption dataModel,
 | |
|             DbProviderManifest providerManifest,
 | |
|             out IList<Schema> schemaCollection)
 | |
|         {
 | |
|             return ParseAndValidate(xmlReaders, 
 | |
|                 sourceFilePaths, 
 | |
|                 dataModel,
 | |
|                 NoOpAttributeValueNotification,
 | |
|                 NoOpAttributeValueNotification,
 | |
|                 delegate(Action<string, ErrorCode, EdmSchemaErrorSeverity> addError) { return providerManifest == null ? MetadataItem.EdmProviderManifest : providerManifest; },
 | |
|                 out schemaCollection);
 | |
|         }
 | |
| 
 | |
|         public static IList<EdmSchemaError> ParseAndValidate(IEnumerable<XmlReader> xmlReaders,
 | |
|             IEnumerable<string> sourceFilePaths, SchemaDataModelOption dataModel,
 | |
|             AttributeValueNotification providerNotification,
 | |
|             AttributeValueNotification providerManifestTokenNotification,
 | |
|             ProviderManifestNeeded providerManifestNeeded,
 | |
|             out IList<Schema> schemaCollection)
 | |
|         {
 | |
|             SchemaManager schemaManager = new SchemaManager(dataModel, providerNotification, providerManifestTokenNotification, providerManifestNeeded);
 | |
|             var errorCollection = new List<EdmSchemaError>();
 | |
|             schemaCollection = new List<Schema>();
 | |
|             bool errorEncountered = false;
 | |
| 
 | |
|             List<string> filePathList;
 | |
|             if (sourceFilePaths != null)
 | |
|             {
 | |
|                 filePathList = new List<string>(sourceFilePaths);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 filePathList = new List<string>();
 | |
|             }
 | |
| 
 | |
|             int index = 0;
 | |
|             foreach (XmlReader xmlReader in xmlReaders)
 | |
|             {
 | |
|                 string location = null;
 | |
|                 if (filePathList.Count <= index)
 | |
|                 {
 | |
|                     TryGetBaseUri(xmlReader, out location);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     location = filePathList[index];
 | |
|                 }
 | |
| 
 | |
|                 Schema schema;
 | |
|                 schema = new Schema(schemaManager);
 | |
| 
 | |
|                 var errorsForCurrentSchema = schema.Parse(xmlReader, location);
 | |
| 
 | |
|                 CheckIsSameVersion(schema, schemaCollection, errorCollection);
 | |
|                 
 | |
|                 // If the number of errors exceeded the max error count, then return
 | |
|                 if (UpdateErrorCollectionAndCheckForMaxErrors(errorCollection, errorsForCurrentSchema, ref errorEncountered))
 | |
|                 {
 | |
|                     return errorCollection;
 | |
|                 }
 | |
| 
 | |
|                 // Add the schema to the collection if there are no errors. There are errors in which schema do not have any namespace.
 | |
|                 // Also if there is an error encountered in one of the schema, we do not need to add the remaining schemas since
 | |
|                 // we will never go to the resolve phase.
 | |
|                 if (!errorEncountered)
 | |
|                 {
 | |
|                     schemaCollection.Add(schema);
 | |
|                     schemaManager.AddSchema(schema);
 | |
|                     var currentSchemaVersion = schema.SchemaVersion;
 | |
|                     Debug.Assert(schemaCollection.All(s => s.SchemaVersion == currentSchemaVersion || s.SchemaVersion != XmlConstants.UndefinedVersion));
 | |
|                 }
 | |
|                 index++;
 | |
|             }
 | |
| 
 | |
|             // If there are no errors encountered in the parsing stage, we can proceed to the 
 | |
|             // parsing and validating phase
 | |
|             if (!errorEncountered)
 | |
|             {
 | |
|                 foreach (Schema schema in schemaCollection)
 | |
|                 {
 | |
|                     if (UpdateErrorCollectionAndCheckForMaxErrors(errorCollection, schema.Resolve(), ref errorEncountered))
 | |
|                     {
 | |
|                         return errorCollection;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 // If there are no errors encountered in the parsing stage, we can proceed to the 
 | |
|                 // parsing and validating phase
 | |
|                 if (!errorEncountered)
 | |
|                 {
 | |
|                     foreach (Schema schema in schemaCollection)
 | |
|                     {
 | |
|                         if (UpdateErrorCollectionAndCheckForMaxErrors(errorCollection, schema.ValidateSchema(), ref errorEncountered))
 | |
|                         {
 | |
|                             return errorCollection;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             return errorCollection;
 | |
|         }
 | |
| 
 | |
|         // this method will move skip down to the first element, or to the end if it doesn't find one
 | |
|         internal static bool TryGetSchemaVersion(XmlReader reader, out double version, out DataSpace dataSpace)
 | |
|         {
 | |
|             // to make life simpler, we skip down to the first/root element, unless we're
 | |
|             // already there
 | |
|             if (!reader.EOF && reader.NodeType != XmlNodeType.Element)
 | |
|             {
 | |
|                 while (reader.Read() && reader.NodeType != XmlNodeType.Element)
 | |
|                 {
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (!reader.EOF &&
 | |
|                 (reader.LocalName == XmlConstants.Schema || reader.LocalName == StorageMslConstructs.MappingElement))
 | |
|             {
 | |
|                 return TryGetSchemaVersion(reader.NamespaceURI, out version, out dataSpace);
 | |
|             }
 | |
| 
 | |
|             version = default(double);
 | |
|             dataSpace = default(DataSpace);
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         internal static bool TryGetSchemaVersion(string xmlNamespaceName, out double version, out DataSpace dataSpace)
 | |
|         {
 | |
|             switch (xmlNamespaceName)
 | |
|             {
 | |
|                 case XmlConstants.ModelNamespace_1:
 | |
|                     version = XmlConstants.EdmVersionForV1;
 | |
|                     dataSpace = DataSpace.CSpace;
 | |
|                     return true;
 | |
|                 case XmlConstants.ModelNamespace_1_1:
 | |
|                     version = XmlConstants.EdmVersionForV1_1;
 | |
|                     dataSpace = DataSpace.CSpace;
 | |
|                     return true;
 | |
|                 case XmlConstants.ModelNamespace_2:
 | |
|                     version = XmlConstants.EdmVersionForV2;
 | |
|                     dataSpace = DataSpace.CSpace;
 | |
|                     return true;
 | |
|                 case XmlConstants.ModelNamespace_3:
 | |
|                     version = XmlConstants.EdmVersionForV3;
 | |
|                     dataSpace = DataSpace.CSpace;
 | |
|                     return true;
 | |
|                 case XmlConstants.TargetNamespace_1:
 | |
|                     version = XmlConstants.StoreVersionForV1;
 | |
|                     dataSpace = DataSpace.SSpace;
 | |
|                     return true;
 | |
|                 case XmlConstants.TargetNamespace_2:
 | |
|                     version = XmlConstants.StoreVersionForV2;
 | |
|                     dataSpace = DataSpace.SSpace;
 | |
|                     return true;
 | |
|                 case XmlConstants.TargetNamespace_3:
 | |
|                     version = XmlConstants.StoreVersionForV3;
 | |
|                     dataSpace = DataSpace.SSpace;
 | |
|                     return true;
 | |
|                 case StorageMslConstructs.NamespaceUriV1:
 | |
|                     version = StorageMslConstructs.MappingVersionV1;
 | |
|                     dataSpace = DataSpace.CSSpace;
 | |
|                     return true;
 | |
|                 case StorageMslConstructs.NamespaceUriV2:
 | |
|                     version = StorageMslConstructs.MappingVersionV2;
 | |
|                     dataSpace = DataSpace.CSSpace;
 | |
|                     return true;
 | |
|                 case StorageMslConstructs.NamespaceUriV3:
 | |
|                     version = StorageMslConstructs.MappingVersionV3;
 | |
|                     dataSpace = DataSpace.CSSpace;
 | |
|                     return true;
 | |
|                 default:
 | |
|                     version = default(Double);
 | |
|                     dataSpace = default(DataSpace);
 | |
|                     return false;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private static bool CheckIsSameVersion(Schema schemaToBeAdded, IEnumerable<Schema> schemaCollection, List<EdmSchemaError> errorCollection)
 | |
|         {
 | |
|             if (schemaToBeAdded.SchemaVersion != XmlConstants.UndefinedVersion && schemaCollection.Count() > 0)
 | |
|             {
 | |
|                 if (schemaCollection.Any(s => s.SchemaVersion != XmlConstants.UndefinedVersion && s.SchemaVersion != schemaToBeAdded.SchemaVersion))
 | |
|                 {
 | |
|                     errorCollection.Add(
 | |
|                     new EdmSchemaError(
 | |
|                         Strings.CannotLoadDifferentVersionOfSchemaInTheSameItemCollection,
 | |
|                         (int)ErrorCode.CannotLoadDifferentVersionOfSchemaInTheSameItemCollection,
 | |
|                         EdmSchemaErrorSeverity.Error));
 | |
|                 }
 | |
|             }
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         public double SchemaVersion { get { return this.effectiveSchemaVersion; } }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Add the namespace of the given schema to the namespace lookup table
 | |
|         /// </summary>
 | |
|         public void AddSchema(Schema schema)
 | |
|         {
 | |
|             Debug.Assert(schema.DataModel == _dataModel, "DataModel must match");
 | |
| 
 | |
|             if (_namespaceLookUpTable.Count == 0 && schema.DataModel != SchemaDataModelOption.ProviderManifestModel)
 | |
|             {
 | |
|                 // Add the primitive type namespace to the namespace look up table
 | |
|                 if (this.PrimitiveSchema.Namespace != null)
 | |
|                 {
 | |
|                     _namespaceLookUpTable.Add(this.PrimitiveSchema.Namespace);
 | |
|                 }
 | |
|             }
 | |
|                                     
 | |
|             // Add the namespace to the namespaceLookUpTable. 
 | |
|             // Its okay to have multiple schemas with the same namespace
 | |
|             _namespaceLookUpTable.Add(schema.Namespace);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Resolve the type - if the type is not found, return appropriate error
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         public bool TryResolveType(string namespaceName, string typeName, out SchemaType schemaType)
 | |
|         {
 | |
|             // For resolving entity container names, namespace can be null
 | |
|             string fullyQualifiedName = String.IsNullOrEmpty(namespaceName) ? typeName : namespaceName + "." + typeName;
 | |
| 
 | |
|             schemaType = SchemaTypes.LookUpEquivalentKey(fullyQualifiedName);
 | |
|             if (schemaType != null)
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns true if this is a valid namespace name or else returns false
 | |
|         /// </summary>
 | |
|         public bool IsValidNamespaceName(string namespaceName)
 | |
|         {
 | |
|             return _namespaceLookUpTable.Contains(namespaceName);
 | |
|         }
 | |
|         #endregion // Public Methods
 | |
| 
 | |
|         #region Private Methods
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Checks if the xml reader has base uri. If it doesn't have, it adds error, other
 | |
|         /// returns the location from the base uri
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         internal static bool TryGetBaseUri(XmlReader xmlReader, out string location)
 | |
|         {
 | |
|             string baseUri = xmlReader.BaseURI;
 | |
|             Uri uri = null;
 | |
| 
 | |
|             if (!string.IsNullOrEmpty(baseUri) &&
 | |
|                  Uri.TryCreate(baseUri, UriKind.Absolute, out uri) &&
 | |
|                  uri.Scheme == "file")
 | |
|             {
 | |
|                 location = Helper.GetFileNameFromUri(uri);
 | |
|                 return true;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 location = null;                
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Add the given list of newErrors to the error collection. If there is a error in the new errors,
 | |
|         /// it sets the errorEncountered to true. Returns true if the number of errors encountered is more 
 | |
|         /// than max errors
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         private static bool UpdateErrorCollectionAndCheckForMaxErrors(List<EdmSchemaError> errorCollection, 
 | |
|             IList<EdmSchemaError> newErrors, ref bool errorEncountered)
 | |
|         {
 | |
|             // If we have encountered error already in one of the schemas, then we don't need to check for errors in the remaining schemas.
 | |
|             // Just keep aggregating the errors and throw them at the end.
 | |
|             if (!errorEncountered)
 | |
|             {
 | |
|                 if (!MetadataHelper.CheckIfAllErrorsAreWarnings(newErrors))
 | |
|                 {
 | |
|                     errorEncountered = true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Add the new errors to the error collection
 | |
|             errorCollection.AddRange(newErrors);
 | |
| 
 | |
|             if (errorEncountered && 
 | |
|                 errorCollection.Where(e => e.Severity == EdmSchemaErrorSeverity.Error).Count() > MaxErrorCount)
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
|             return false;
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Internal Properties
 | |
| 
 | |
|         internal SchemaElementLookUpTable<SchemaType> SchemaTypes
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return _schemaTypes;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal DbProviderManifest GetProviderManifest(Action<string, ErrorCode, EdmSchemaErrorSeverity> addError)
 | |
|         { 
 | |
|             if (_providerManifest == null)
 | |
|             {
 | |
|                 _providerManifest = _providerManifestNeeded(addError);
 | |
|             }
 | |
|             return _providerManifest;
 | |
|         }
 | |
| 
 | |
|         internal SchemaDataModelOption DataModel { get { return _dataModel; } }
 | |
| 
 | |
|         internal void EnsurePrimitiveSchemaIsLoaded(double forSchemaVersion)
 | |
|         {
 | |
|             if (_primitiveSchema == null)
 | |
|             {
 | |
|                 this.effectiveSchemaVersion = forSchemaVersion;
 | |
|                 _primitiveSchema = new PrimitiveSchema(this);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal PrimitiveSchema PrimitiveSchema
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return _primitiveSchema;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal AttributeValueNotification ProviderNotification
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return _providerNotification;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal AttributeValueNotification ProviderManifestTokenNotification
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return _providerManifestTokenNotification;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
|     }
 | |
| }
 |