1 //---------------------------------------------------------------------
2 // <copyright file="SchemaManager.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 using System.Collections;
11 using System.Collections.Generic;
12 using System.Data.Common;
13 using System.Data.Common.Utils;
14 using System.Data.Entity;
15 using System.Data.Mapping;
16 using System.Data.Metadata.Edm;
17 using System.Diagnostics;
21 namespace System.Data.EntityModel.SchemaObjectModel
23 internal delegate void AttributeValueNotification(string token, Action<string, ErrorCode, EdmSchemaErrorSeverity> addError);
24 internal delegate DbProviderManifest ProviderManifestNeeded(Action<string, ErrorCode, EdmSchemaErrorSeverity> addError);
27 /// Class responsible for parsing,validating a collection of schema
29 [System.Diagnostics.DebuggerDisplay("DataModel={DataModel}")]
30 internal class SchemaManager
32 #region Instance Fields
33 // This keeps track of all the possible namespaces encountered till now. This helps in displaying the error to the
34 // user - if the particular type is not found, we can report whether the namespace was invalid or the type with the
35 // given name was not found in the given namespace. This also helps in validating the namespace in the using elements
36 private readonly HashSet<string> _namespaceLookUpTable = new HashSet<string>(StringComparer.Ordinal);
38 // List of all the schema types across all the schemas. This is to ensure that there is no duplicate type encountered
40 private readonly SchemaElementLookUpTable<SchemaType> _schemaTypes = new SchemaElementLookUpTable<SchemaType>();
42 // We want to stop parsing/resolving/validation after the first 100 errors
43 private const int MaxErrorCount = 100;
46 private DbProviderManifest _providerManifest;
47 private PrimitiveSchema _primitiveSchema;
48 private double effectiveSchemaVersion = XmlConstants.UndefinedVersion;
50 private readonly SchemaDataModelOption _dataModel;
51 private readonly ProviderManifestNeeded _providerManifestNeeded;
52 private readonly AttributeValueNotification _providerNotification;
53 private readonly AttributeValueNotification _providerManifestTokenNotification;
57 private SchemaManager(SchemaDataModelOption dataModel, AttributeValueNotification providerNotification, AttributeValueNotification providerManifestTokenNotification, ProviderManifestNeeded providerManifestNeeded)
59 _dataModel = dataModel;
60 _providerNotification = providerNotification;
61 _providerManifestTokenNotification = providerManifestTokenNotification;
62 _providerManifestNeeded = providerManifestNeeded;
66 #region Public Methods
70 public static IList<EdmSchemaError> LoadProviderManifest(XmlReader xmlReader, string location,
71 bool checkForSystemNamespace, out Schema schema)
73 IList<Schema> schemaCollection = new List<Schema>(1);
75 DbProviderManifest providerManifest = checkForSystemNamespace ? EdmProviderManifest.Instance : null;
76 IList<EdmSchemaError> errors = ParseAndValidate(new XmlReader[] { xmlReader },
77 new string[] { location }, SchemaDataModelOption.ProviderManifestModel,
78 providerManifest, out schemaCollection);
80 // In case of errors, there are no schema in the schema collection
81 if (schemaCollection.Count != 0)
83 schema = schemaCollection[0];
87 Debug.Assert(errors.Count != 0, "There must be some error encountered");
94 public static void NoOpAttributeValueNotification(string attributeValue, Action<string, ErrorCode, EdmSchemaErrorSeverity> addError) { }
96 public static IList<EdmSchemaError> ParseAndValidate(IEnumerable<XmlReader> xmlReaders,
97 IEnumerable<string> sourceFilePaths, SchemaDataModelOption dataModel,
98 DbProviderManifest providerManifest,
99 out IList<Schema> schemaCollection)
101 return ParseAndValidate(xmlReaders,
104 NoOpAttributeValueNotification,
105 NoOpAttributeValueNotification,
106 delegate(Action<string, ErrorCode, EdmSchemaErrorSeverity> addError) { return providerManifest == null ? MetadataItem.EdmProviderManifest : providerManifest; },
107 out schemaCollection);
110 public static IList<EdmSchemaError> ParseAndValidate(IEnumerable<XmlReader> xmlReaders,
111 IEnumerable<string> sourceFilePaths, SchemaDataModelOption dataModel,
112 AttributeValueNotification providerNotification,
113 AttributeValueNotification providerManifestTokenNotification,
114 ProviderManifestNeeded providerManifestNeeded,
115 out IList<Schema> schemaCollection)
117 SchemaManager schemaManager = new SchemaManager(dataModel, providerNotification, providerManifestTokenNotification, providerManifestNeeded);
118 var errorCollection = new List<EdmSchemaError>();
119 schemaCollection = new List<Schema>();
120 bool errorEncountered = false;
122 List<string> filePathList;
123 if (sourceFilePaths != null)
125 filePathList = new List<string>(sourceFilePaths);
129 filePathList = new List<string>();
133 foreach (XmlReader xmlReader in xmlReaders)
135 string location = null;
136 if (filePathList.Count <= index)
138 TryGetBaseUri(xmlReader, out location);
142 location = filePathList[index];
146 schema = new Schema(schemaManager);
148 var errorsForCurrentSchema = schema.Parse(xmlReader, location);
150 CheckIsSameVersion(schema, schemaCollection, errorCollection);
152 // If the number of errors exceeded the max error count, then return
153 if (UpdateErrorCollectionAndCheckForMaxErrors(errorCollection, errorsForCurrentSchema, ref errorEncountered))
155 return errorCollection;
158 // Add the schema to the collection if there are no errors. There are errors in which schema do not have any namespace.
159 // Also if there is an error encountered in one of the schema, we do not need to add the remaining schemas since
160 // we will never go to the resolve phase.
161 if (!errorEncountered)
163 schemaCollection.Add(schema);
164 schemaManager.AddSchema(schema);
165 var currentSchemaVersion = schema.SchemaVersion;
166 Debug.Assert(schemaCollection.All(s => s.SchemaVersion == currentSchemaVersion || s.SchemaVersion != XmlConstants.UndefinedVersion));
171 // If there are no errors encountered in the parsing stage, we can proceed to the
172 // parsing and validating phase
173 if (!errorEncountered)
175 foreach (Schema schema in schemaCollection)
177 if (UpdateErrorCollectionAndCheckForMaxErrors(errorCollection, schema.Resolve(), ref errorEncountered))
179 return errorCollection;
183 // If there are no errors encountered in the parsing stage, we can proceed to the
184 // parsing and validating phase
185 if (!errorEncountered)
187 foreach (Schema schema in schemaCollection)
189 if (UpdateErrorCollectionAndCheckForMaxErrors(errorCollection, schema.ValidateSchema(), ref errorEncountered))
191 return errorCollection;
197 return errorCollection;
200 // this method will move skip down to the first element, or to the end if it doesn't find one
201 internal static bool TryGetSchemaVersion(XmlReader reader, out double version, out DataSpace dataSpace)
203 // to make life simpler, we skip down to the first/root element, unless we're
205 if (!reader.EOF && reader.NodeType != XmlNodeType.Element)
207 while (reader.Read() && reader.NodeType != XmlNodeType.Element)
213 (reader.LocalName == XmlConstants.Schema || reader.LocalName == StorageMslConstructs.MappingElement))
215 return TryGetSchemaVersion(reader.NamespaceURI, out version, out dataSpace);
218 version = default(double);
219 dataSpace = default(DataSpace);
223 internal static bool TryGetSchemaVersion(string xmlNamespaceName, out double version, out DataSpace dataSpace)
225 switch (xmlNamespaceName)
227 case XmlConstants.ModelNamespace_1:
228 version = XmlConstants.EdmVersionForV1;
229 dataSpace = DataSpace.CSpace;
231 case XmlConstants.ModelNamespace_1_1:
232 version = XmlConstants.EdmVersionForV1_1;
233 dataSpace = DataSpace.CSpace;
235 case XmlConstants.ModelNamespace_2:
236 version = XmlConstants.EdmVersionForV2;
237 dataSpace = DataSpace.CSpace;
239 case XmlConstants.ModelNamespace_3:
240 version = XmlConstants.EdmVersionForV3;
241 dataSpace = DataSpace.CSpace;
243 case XmlConstants.TargetNamespace_1:
244 version = XmlConstants.StoreVersionForV1;
245 dataSpace = DataSpace.SSpace;
247 case XmlConstants.TargetNamespace_2:
248 version = XmlConstants.StoreVersionForV2;
249 dataSpace = DataSpace.SSpace;
251 case XmlConstants.TargetNamespace_3:
252 version = XmlConstants.StoreVersionForV3;
253 dataSpace = DataSpace.SSpace;
255 case StorageMslConstructs.NamespaceUriV1:
256 version = StorageMslConstructs.MappingVersionV1;
257 dataSpace = DataSpace.CSSpace;
259 case StorageMslConstructs.NamespaceUriV2:
260 version = StorageMslConstructs.MappingVersionV2;
261 dataSpace = DataSpace.CSSpace;
263 case StorageMslConstructs.NamespaceUriV3:
264 version = StorageMslConstructs.MappingVersionV3;
265 dataSpace = DataSpace.CSSpace;
268 version = default(Double);
269 dataSpace = default(DataSpace);
274 private static bool CheckIsSameVersion(Schema schemaToBeAdded, IEnumerable<Schema> schemaCollection, List<EdmSchemaError> errorCollection)
276 if (schemaToBeAdded.SchemaVersion != XmlConstants.UndefinedVersion && schemaCollection.Count() > 0)
278 if (schemaCollection.Any(s => s.SchemaVersion != XmlConstants.UndefinedVersion && s.SchemaVersion != schemaToBeAdded.SchemaVersion))
282 Strings.CannotLoadDifferentVersionOfSchemaInTheSameItemCollection,
283 (int)ErrorCode.CannotLoadDifferentVersionOfSchemaInTheSameItemCollection,
284 EdmSchemaErrorSeverity.Error));
290 public double SchemaVersion { get { return this.effectiveSchemaVersion; } }
293 /// Add the namespace of the given schema to the namespace lookup table
295 public void AddSchema(Schema schema)
297 Debug.Assert(schema.DataModel == _dataModel, "DataModel must match");
299 if (_namespaceLookUpTable.Count == 0 && schema.DataModel != SchemaDataModelOption.ProviderManifestModel)
301 // Add the primitive type namespace to the namespace look up table
302 if (this.PrimitiveSchema.Namespace != null)
304 _namespaceLookUpTable.Add(this.PrimitiveSchema.Namespace);
308 // Add the namespace to the namespaceLookUpTable.
309 // Its okay to have multiple schemas with the same namespace
310 _namespaceLookUpTable.Add(schema.Namespace);
314 /// Resolve the type - if the type is not found, return appropriate error
316 /// <returns></returns>
317 public bool TryResolveType(string namespaceName, string typeName, out SchemaType schemaType)
319 // For resolving entity container names, namespace can be null
320 string fullyQualifiedName = String.IsNullOrEmpty(namespaceName) ? typeName : namespaceName + "." + typeName;
322 schemaType = SchemaTypes.LookUpEquivalentKey(fullyQualifiedName);
323 if (schemaType != null)
332 /// Returns true if this is a valid namespace name or else returns false
334 public bool IsValidNamespaceName(string namespaceName)
336 return _namespaceLookUpTable.Contains(namespaceName);
338 #endregion // Public Methods
340 #region Private Methods
343 /// Checks if the xml reader has base uri. If it doesn't have, it adds error, other
344 /// returns the location from the base uri
346 /// <returns></returns>
347 internal static bool TryGetBaseUri(XmlReader xmlReader, out string location)
349 string baseUri = xmlReader.BaseURI;
352 if (!string.IsNullOrEmpty(baseUri) &&
353 Uri.TryCreate(baseUri, UriKind.Absolute, out uri) &&
354 uri.Scheme == "file")
356 location = Helper.GetFileNameFromUri(uri);
367 /// Add the given list of newErrors to the error collection. If there is a error in the new errors,
368 /// it sets the errorEncountered to true. Returns true if the number of errors encountered is more
371 /// <returns></returns>
372 private static bool UpdateErrorCollectionAndCheckForMaxErrors(List<EdmSchemaError> errorCollection,
373 IList<EdmSchemaError> newErrors, ref bool errorEncountered)
375 // If we have encountered error already in one of the schemas, then we don't need to check for errors in the remaining schemas.
376 // Just keep aggregating the errors and throw them at the end.
377 if (!errorEncountered)
379 if (!MetadataHelper.CheckIfAllErrorsAreWarnings(newErrors))
381 errorEncountered = true;
385 // Add the new errors to the error collection
386 errorCollection.AddRange(newErrors);
388 if (errorEncountered &&
389 errorCollection.Where(e => e.Severity == EdmSchemaErrorSeverity.Error).Count() > MaxErrorCount)
397 #region Internal Properties
399 internal SchemaElementLookUpTable<SchemaType> SchemaTypes
407 internal DbProviderManifest GetProviderManifest(Action<string, ErrorCode, EdmSchemaErrorSeverity> addError)
409 if (_providerManifest == null)
411 _providerManifest = _providerManifestNeeded(addError);
413 return _providerManifest;
416 internal SchemaDataModelOption DataModel { get { return _dataModel; } }
418 internal void EnsurePrimitiveSchemaIsLoaded(double forSchemaVersion)
420 if (_primitiveSchema == null)
422 this.effectiveSchemaVersion = forSchemaVersion;
423 _primitiveSchema = new PrimitiveSchema(this);
427 internal PrimitiveSchema PrimitiveSchema
431 return _primitiveSchema;
435 internal AttributeValueNotification ProviderNotification
439 return _providerNotification;
443 internal AttributeValueNotification ProviderManifestTokenNotification
447 return _providerManifestTokenNotification;