Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / EntityModel / SchemaObjectModel / SchemaManager.cs
1 //---------------------------------------------------------------------
2 // <copyright file="SchemaManager.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
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;
18 using System.Linq;
19 using System.Xml;
20
21 namespace System.Data.EntityModel.SchemaObjectModel
22 {
23     internal delegate void AttributeValueNotification(string token, Action<string, ErrorCode, EdmSchemaErrorSeverity> addError);
24     internal delegate DbProviderManifest ProviderManifestNeeded(Action<string, ErrorCode, EdmSchemaErrorSeverity> addError);
25
26     /// <summary>
27     /// Class responsible for parsing,validating a collection of schema
28     /// </summary>
29     [System.Diagnostics.DebuggerDisplay("DataModel={DataModel}")]
30     internal class SchemaManager
31     {
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);
37
38         // List of all the schema types across all the schemas. This is to ensure that there is no duplicate type encountered
39         // across schemas
40         private readonly SchemaElementLookUpTable<SchemaType> _schemaTypes = new SchemaElementLookUpTable<SchemaType>();
41
42         // We want to stop parsing/resolving/validation after the first 100 errors
43         private const int MaxErrorCount = 100;
44
45         // delay loaded
46         private DbProviderManifest _providerManifest;
47         private PrimitiveSchema _primitiveSchema;
48         private double effectiveSchemaVersion = XmlConstants.UndefinedVersion;
49
50         private readonly SchemaDataModelOption _dataModel;
51         private readonly ProviderManifestNeeded _providerManifestNeeded;
52         private readonly AttributeValueNotification _providerNotification;
53         private readonly AttributeValueNotification _providerManifestTokenNotification;
54         #endregion
55
56         #region Constructor
57         private SchemaManager(SchemaDataModelOption dataModel, AttributeValueNotification providerNotification, AttributeValueNotification providerManifestTokenNotification, ProviderManifestNeeded providerManifestNeeded)
58         {
59             _dataModel = dataModel;
60             _providerNotification = providerNotification;
61             _providerManifestTokenNotification = providerManifestTokenNotification;
62             _providerManifestNeeded = providerManifestNeeded;
63         }
64         #endregion
65
66         #region Public Methods
67
68
69         
70         public static IList<EdmSchemaError> LoadProviderManifest(XmlReader xmlReader, string location,
71             bool checkForSystemNamespace, out Schema schema)
72         {
73             IList<Schema> schemaCollection = new List<Schema>(1);
74
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);
79
80             // In case of errors, there are no schema in the schema collection
81             if (schemaCollection.Count != 0)
82             {
83                 schema = schemaCollection[0];
84             }
85             else
86             {
87                 Debug.Assert(errors.Count != 0, "There must be some error encountered");
88                 schema = null;
89             }
90
91             return errors;
92         }
93
94         public static void NoOpAttributeValueNotification(string attributeValue, Action<string, ErrorCode, EdmSchemaErrorSeverity> addError) { }
95         
96         public static IList<EdmSchemaError> ParseAndValidate(IEnumerable<XmlReader> xmlReaders,
97             IEnumerable<string> sourceFilePaths, SchemaDataModelOption dataModel,
98             DbProviderManifest providerManifest,
99             out IList<Schema> schemaCollection)
100         {
101             return ParseAndValidate(xmlReaders, 
102                 sourceFilePaths, 
103                 dataModel,
104                 NoOpAttributeValueNotification,
105                 NoOpAttributeValueNotification,
106                 delegate(Action<string, ErrorCode, EdmSchemaErrorSeverity> addError) { return providerManifest == null ? MetadataItem.EdmProviderManifest : providerManifest; },
107                 out schemaCollection);
108         }
109
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)
116         {
117             SchemaManager schemaManager = new SchemaManager(dataModel, providerNotification, providerManifestTokenNotification, providerManifestNeeded);
118             var errorCollection = new List<EdmSchemaError>();
119             schemaCollection = new List<Schema>();
120             bool errorEncountered = false;
121
122             List<string> filePathList;
123             if (sourceFilePaths != null)
124             {
125                 filePathList = new List<string>(sourceFilePaths);
126             }
127             else
128             {
129                 filePathList = new List<string>();
130             }
131
132             int index = 0;
133             foreach (XmlReader xmlReader in xmlReaders)
134             {
135                 string location = null;
136                 if (filePathList.Count <= index)
137                 {
138                     TryGetBaseUri(xmlReader, out location);
139                 }
140                 else
141                 {
142                     location = filePathList[index];
143                 }
144
145                 Schema schema;
146                 schema = new Schema(schemaManager);
147
148                 var errorsForCurrentSchema = schema.Parse(xmlReader, location);
149
150                 CheckIsSameVersion(schema, schemaCollection, errorCollection);
151                 
152                 // If the number of errors exceeded the max error count, then return
153                 if (UpdateErrorCollectionAndCheckForMaxErrors(errorCollection, errorsForCurrentSchema, ref errorEncountered))
154                 {
155                     return errorCollection;
156                 }
157
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)
162                 {
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));
167                 }
168                 index++;
169             }
170
171             // If there are no errors encountered in the parsing stage, we can proceed to the 
172             // parsing and validating phase
173             if (!errorEncountered)
174             {
175                 foreach (Schema schema in schemaCollection)
176                 {
177                     if (UpdateErrorCollectionAndCheckForMaxErrors(errorCollection, schema.Resolve(), ref errorEncountered))
178                     {
179                         return errorCollection;
180                     }
181                 }
182
183                 // If there are no errors encountered in the parsing stage, we can proceed to the 
184                 // parsing and validating phase
185                 if (!errorEncountered)
186                 {
187                     foreach (Schema schema in schemaCollection)
188                     {
189                         if (UpdateErrorCollectionAndCheckForMaxErrors(errorCollection, schema.ValidateSchema(), ref errorEncountered))
190                         {
191                             return errorCollection;
192                         }
193                     }
194                 }
195             }
196             
197             return errorCollection;
198         }
199
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)
202         {
203             // to make life simpler, we skip down to the first/root element, unless we're
204             // already there
205             if (!reader.EOF && reader.NodeType != XmlNodeType.Element)
206             {
207                 while (reader.Read() && reader.NodeType != XmlNodeType.Element)
208                 {
209                 }
210             }
211
212             if (!reader.EOF &&
213                 (reader.LocalName == XmlConstants.Schema || reader.LocalName == StorageMslConstructs.MappingElement))
214             {
215                 return TryGetSchemaVersion(reader.NamespaceURI, out version, out dataSpace);
216             }
217
218             version = default(double);
219             dataSpace = default(DataSpace);
220             return false;
221         }
222
223         internal static bool TryGetSchemaVersion(string xmlNamespaceName, out double version, out DataSpace dataSpace)
224         {
225             switch (xmlNamespaceName)
226             {
227                 case XmlConstants.ModelNamespace_1:
228                     version = XmlConstants.EdmVersionForV1;
229                     dataSpace = DataSpace.CSpace;
230                     return true;
231                 case XmlConstants.ModelNamespace_1_1:
232                     version = XmlConstants.EdmVersionForV1_1;
233                     dataSpace = DataSpace.CSpace;
234                     return true;
235                 case XmlConstants.ModelNamespace_2:
236                     version = XmlConstants.EdmVersionForV2;
237                     dataSpace = DataSpace.CSpace;
238                     return true;
239                 case XmlConstants.ModelNamespace_3:
240                     version = XmlConstants.EdmVersionForV3;
241                     dataSpace = DataSpace.CSpace;
242                     return true;
243                 case XmlConstants.TargetNamespace_1:
244                     version = XmlConstants.StoreVersionForV1;
245                     dataSpace = DataSpace.SSpace;
246                     return true;
247                 case XmlConstants.TargetNamespace_2:
248                     version = XmlConstants.StoreVersionForV2;
249                     dataSpace = DataSpace.SSpace;
250                     return true;
251                 case XmlConstants.TargetNamespace_3:
252                     version = XmlConstants.StoreVersionForV3;
253                     dataSpace = DataSpace.SSpace;
254                     return true;
255                 case StorageMslConstructs.NamespaceUriV1:
256                     version = StorageMslConstructs.MappingVersionV1;
257                     dataSpace = DataSpace.CSSpace;
258                     return true;
259                 case StorageMslConstructs.NamespaceUriV2:
260                     version = StorageMslConstructs.MappingVersionV2;
261                     dataSpace = DataSpace.CSSpace;
262                     return true;
263                 case StorageMslConstructs.NamespaceUriV3:
264                     version = StorageMslConstructs.MappingVersionV3;
265                     dataSpace = DataSpace.CSSpace;
266                     return true;
267                 default:
268                     version = default(Double);
269                     dataSpace = default(DataSpace);
270                     return false;
271             }
272         }
273
274         private static bool CheckIsSameVersion(Schema schemaToBeAdded, IEnumerable<Schema> schemaCollection, List<EdmSchemaError> errorCollection)
275         {
276             if (schemaToBeAdded.SchemaVersion != XmlConstants.UndefinedVersion && schemaCollection.Count() > 0)
277             {
278                 if (schemaCollection.Any(s => s.SchemaVersion != XmlConstants.UndefinedVersion && s.SchemaVersion != schemaToBeAdded.SchemaVersion))
279                 {
280                     errorCollection.Add(
281                     new EdmSchemaError(
282                         Strings.CannotLoadDifferentVersionOfSchemaInTheSameItemCollection,
283                         (int)ErrorCode.CannotLoadDifferentVersionOfSchemaInTheSameItemCollection,
284                         EdmSchemaErrorSeverity.Error));
285                 }
286             }
287             return true;
288         }
289
290         public double SchemaVersion { get { return this.effectiveSchemaVersion; } }
291
292         /// <summary>
293         /// Add the namespace of the given schema to the namespace lookup table
294         /// </summary>
295         public void AddSchema(Schema schema)
296         {
297             Debug.Assert(schema.DataModel == _dataModel, "DataModel must match");
298
299             if (_namespaceLookUpTable.Count == 0 && schema.DataModel != SchemaDataModelOption.ProviderManifestModel)
300             {
301                 // Add the primitive type namespace to the namespace look up table
302                 if (this.PrimitiveSchema.Namespace != null)
303                 {
304                     _namespaceLookUpTable.Add(this.PrimitiveSchema.Namespace);
305                 }
306             }
307                                     
308             // Add the namespace to the namespaceLookUpTable. 
309             // Its okay to have multiple schemas with the same namespace
310             _namespaceLookUpTable.Add(schema.Namespace);
311         }
312
313         /// <summary>
314         /// Resolve the type - if the type is not found, return appropriate error
315         /// </summary>
316         /// <returns></returns>
317         public bool TryResolveType(string namespaceName, string typeName, out SchemaType schemaType)
318         {
319             // For resolving entity container names, namespace can be null
320             string fullyQualifiedName = String.IsNullOrEmpty(namespaceName) ? typeName : namespaceName + "." + typeName;
321
322             schemaType = SchemaTypes.LookUpEquivalentKey(fullyQualifiedName);
323             if (schemaType != null)
324             {
325                 return true;
326             }
327
328             return false;
329         }
330
331         /// <summary>
332         /// Returns true if this is a valid namespace name or else returns false
333         /// </summary>
334         public bool IsValidNamespaceName(string namespaceName)
335         {
336             return _namespaceLookUpTable.Contains(namespaceName);
337         }
338         #endregion // Public Methods
339
340         #region Private Methods
341
342         /// <summary>
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
345         /// </summary>
346         /// <returns></returns>
347         internal static bool TryGetBaseUri(XmlReader xmlReader, out string location)
348         {
349             string baseUri = xmlReader.BaseURI;
350             Uri uri = null;
351
352             if (!string.IsNullOrEmpty(baseUri) &&
353                  Uri.TryCreate(baseUri, UriKind.Absolute, out uri) &&
354                  uri.Scheme == "file")
355             {
356                 location = Helper.GetFileNameFromUri(uri);
357                 return true;
358             }
359             else
360             {
361                 location = null;                
362                 return false;
363             }
364         }
365
366         /// <summary>
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 
369         /// than max errors
370         /// </summary>
371         /// <returns></returns>
372         private static bool UpdateErrorCollectionAndCheckForMaxErrors(List<EdmSchemaError> errorCollection, 
373             IList<EdmSchemaError> newErrors, ref bool errorEncountered)
374         {
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)
378             {
379                 if (!MetadataHelper.CheckIfAllErrorsAreWarnings(newErrors))
380                 {
381                     errorEncountered = true;
382                 }
383             }
384
385             // Add the new errors to the error collection
386             errorCollection.AddRange(newErrors);
387
388             if (errorEncountered && 
389                 errorCollection.Where(e => e.Severity == EdmSchemaErrorSeverity.Error).Count() > MaxErrorCount)
390             {
391                 return true;
392             }
393             return false;
394         }
395         #endregion
396
397         #region Internal Properties
398
399         internal SchemaElementLookUpTable<SchemaType> SchemaTypes
400         {
401             get
402             {
403                 return _schemaTypes;
404             }
405         }
406
407         internal DbProviderManifest GetProviderManifest(Action<string, ErrorCode, EdmSchemaErrorSeverity> addError)
408         { 
409             if (_providerManifest == null)
410             {
411                 _providerManifest = _providerManifestNeeded(addError);
412             }
413             return _providerManifest;
414         }
415
416         internal SchemaDataModelOption DataModel { get { return _dataModel; } }
417
418         internal void EnsurePrimitiveSchemaIsLoaded(double forSchemaVersion)
419         {
420             if (_primitiveSchema == null)
421             {
422                 this.effectiveSchemaVersion = forSchemaVersion;
423                 _primitiveSchema = new PrimitiveSchema(this);
424             }
425         }
426
427         internal PrimitiveSchema PrimitiveSchema
428         {
429             get
430             {
431                 return _primitiveSchema;
432             }
433         }
434
435         internal AttributeValueNotification ProviderNotification
436         {
437             get
438             {
439                 return _providerNotification;
440             }
441         }
442
443         internal AttributeValueNotification ProviderManifestTokenNotification
444         {
445             get
446             {
447                 return _providerManifestTokenNotification;
448             }
449         }
450
451         #endregion
452     }
453 }