e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
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 [....]
|
|
// @backupOwner [....]
|
|
//---------------------------------------------------------------------
|
|
|
|
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
|
|
}
|
|
}
|