215b129f8b7657e3450dcc78546a51fed30cdc35
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Metadata / MetadataArtifactLoader.cs
1 //---------------------------------------------------------------------
2 // <copyright file="MetadataArtifactLoader.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
10 namespace System.Data.Metadata.Edm
11 {
12     using System.Collections.Generic;
13     using System.Data.Entity;
14     using System.Diagnostics;
15     using System.IO;
16     using System.Runtime.Versioning;
17     using System.Xml;
18
19     /// <summary>
20     /// This is the base class for the resource metadata artifact loader; derived
21     /// classes enacpsulate a single resource as well as collections of resources,
22     /// along the lines of the Composite pattern.
23     /// </summary>
24     internal abstract class MetadataArtifactLoader
25     {
26         protected readonly static string resPathPrefix    = @"res://";
27         protected readonly static string resPathSeparator = @"/";
28         protected readonly static string altPathSeparator = @"\";
29         protected readonly static string wildcard         = @"*";
30
31         /// <summary>
32         /// Read-only access to the resource/file path
33         /// </summary>
34         public abstract string Path{ get; }
35         public abstract void CollectFilePermissionPaths(List<string> paths, DataSpace spaceToGet);
36
37         /// <summary>
38         /// This enum is used to indicate the level of extension check to be perfoemed 
39         /// on a metadata URI.
40         /// </summary>
41         public enum ExtensionCheck
42         {
43             /// <summary>
44             /// Do not perform any extension check
45             /// </summary>
46             None = 0,
47
48             /// <summary>
49             /// Check the extension against a specific value
50             /// </summary>
51             Specific,
52
53             /// <summary>
54             /// Check the extension against the set of acceptable extensions
55             /// </summary>
56             All
57         }
58
59         [ResourceExposure(ResourceScope.Machine)] //Exposes the file name which is a Machine resource
60         [ResourceConsumption(ResourceScope.Machine)] //For Create method call. But the path is not created in this method.
61         public static MetadataArtifactLoader Create(string path,
62                                                     ExtensionCheck extensionCheck,
63                                                     string validExtension,
64                                                     ICollection<string> uriRegistry)
65         {
66             return Create(path, extensionCheck, validExtension, uriRegistry, new DefaultAssemblyResolver());
67         }
68         /// <summary>
69         /// Factory method to create an artifact loader. This is where an appropriate
70         /// subclass of MetadataArtifactLoader is created, depending on the kind of
71         /// resource it will encapsulate.
72         /// </summary>
73         /// <param name="path">The path to the resource(s) to be loaded</param>
74         /// <param name="extensionCheck">Any URI extension checks to perform</param>
75         /// <param name="validExtension">A specific extension for an artifact resource</param>
76         /// <param name="uriRegistry">The global registry of URIs</param>
77         /// <param name="resolveAssembly"></param>
78         /// <returns>A concrete instance of an artifact loader.</returns>
79         [ResourceExposure(ResourceScope.Machine)] //Exposes the file name which is a Machine resource
80         [ResourceConsumption(ResourceScope.Machine)] //For CheckArtifactExtension method call. But the path is not created in this method.
81         internal static MetadataArtifactLoader Create(string path, 
82                                                     ExtensionCheck extensionCheck,
83                                                     string validExtension,
84                                                     ICollection<string> uriRegistry, 
85                                                     MetadataArtifactAssemblyResolver resolver)
86         {
87             Debug.Assert(path != null);
88             Debug.Assert(resolver != null);
89
90             // res:// -based artifacts
91             //
92             if (MetadataArtifactLoader.PathStartsWithResPrefix(path))
93             {
94                 return MetadataArtifactLoaderCompositeResource.CreateResourceLoader(path, extensionCheck, validExtension, uriRegistry, resolver);
95             }
96
97             // Files and Folders
98             //
99             string normalizedPath = MetadataArtifactLoader.NormalizeFilePaths(path);
100             if (Directory.Exists(normalizedPath))
101             {
102                 return new MetadataArtifactLoaderCompositeFile(normalizedPath, uriRegistry);
103             }
104             else if (File.Exists(normalizedPath))
105             {
106                 switch (extensionCheck)
107                 {
108                     case ExtensionCheck.Specific:
109                         CheckArtifactExtension(normalizedPath, validExtension);
110                         break;
111
112                     case ExtensionCheck.All:
113                         if (!MetadataArtifactLoader.IsValidArtifact(normalizedPath))
114                         {
115                             throw EntityUtil.Metadata(Strings.InvalidMetadataPath);
116                         } 
117                         break;
118                 }
119
120                 return new MetadataArtifactLoaderFile(normalizedPath, uriRegistry);
121             }
122
123             throw EntityUtil.Metadata(Strings.InvalidMetadataPath);
124         }
125
126         /// <summary>
127         /// Factory method to create an aggregating artifact loader, one that encapsulates
128         /// multiple collections.
129         /// </summary>
130         /// <param name="allCollections">The list of collections to be aggregated</param>
131         /// <returns>A concrete instance of an artifact loader.</returns>
132         public static MetadataArtifactLoader Create(List<MetadataArtifactLoader> allCollections)
133         {
134             return new MetadataArtifactLoaderComposite(allCollections);
135         }
136
137         /// <summary>
138         /// Helper method that wraps a list of file paths in MetadataArtifactLoader instances.
139         /// </summary>
140         /// <param name="filePaths">The list of file paths to wrap</param>
141         /// <param name="validExtension">An acceptable extension for the file</param>
142         /// <returns>An instance of MetadataArtifactLoader</returns>
143         [ResourceExposure(ResourceScope.Machine)] //Exposes the file names which are a Machine resource
144         [ResourceConsumption(ResourceScope.Machine)] //For CreateCompositeFromFilePaths method call. But the path is not created in this method.
145         public static MetadataArtifactLoader CreateCompositeFromFilePaths(IEnumerable<string> filePaths, string validExtension)
146         {
147             Debug.Assert(!string.IsNullOrEmpty(validExtension));
148
149             return CreateCompositeFromFilePaths(filePaths, validExtension, new DefaultAssemblyResolver());
150         }
151
152         [ResourceExposure(ResourceScope.Machine)] //Exposes the file names which are a Machine resource
153         [ResourceConsumption(ResourceScope.Machine)] //For Create method call. But the paths are not created in this method.
154         internal static MetadataArtifactLoader CreateCompositeFromFilePaths(IEnumerable<string> filePaths, string validExtension, MetadataArtifactAssemblyResolver resolver)
155         {
156             ExtensionCheck extensionCheck;
157             if (string.IsNullOrEmpty(validExtension))
158             {
159                 extensionCheck = ExtensionCheck.All;
160             }
161             else
162             {
163                 extensionCheck = ExtensionCheck.Specific;
164             }
165             
166             List<MetadataArtifactLoader> loaders = new List<MetadataArtifactLoader>();
167
168             // The following set is used to remove duplicate paths from the incoming array
169             HashSet<string> uriRegistry = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
170
171             foreach(string path in filePaths)
172             {
173                 if (string.IsNullOrEmpty(path))
174                 {
175                     throw EntityUtil.Metadata(System.Data.Entity.Strings.NotValidInputPath,
176                                               EntityUtil.CollectionParameterElementIsNullOrEmpty("filePaths"));
177                 }
178
179                 string trimedPath = path.Trim();
180                 if (trimedPath.Length > 0)
181                 {
182                     loaders.Add(MetadataArtifactLoader.Create(
183                                             trimedPath,
184                                             extensionCheck,
185                                             validExtension,
186                                             uriRegistry,
187                                             resolver)
188                                         );
189                 }
190             }
191
192             return MetadataArtifactLoader.Create(loaders);
193         }
194
195         /// <summary>
196         /// Helper method that wraps a collection of XmlReader objects in MetadataArtifactLoader
197         /// instances.
198         /// </summary>
199         /// <param name="filePaths">The collection of XmlReader objects to wrap</param>
200         /// <returns>An instance of MetadataArtifactLoader</returns>
201         public static MetadataArtifactLoader CreateCompositeFromXmlReaders(IEnumerable<XmlReader> xmlReaders)
202         {
203             List<MetadataArtifactLoader> loaders = new List<MetadataArtifactLoader>();
204
205             foreach (XmlReader reader in xmlReaders)
206             {
207                 if (reader == null)
208                 {
209                     throw EntityUtil.CollectionParameterElementIsNull("xmlReaders");
210                 }
211
212                 loaders.Add(new MetadataArtifactLoaderXmlReaderWrapper(reader));
213             }
214
215             return MetadataArtifactLoader.Create(loaders);
216         }
217
218         /// <summary>
219         /// If the path doesn't have the right extension, throw
220         /// </summary>
221         /// <param name="path">The path to the resource</param>
222         /// <param name="validExtension"></param>
223         internal static void CheckArtifactExtension(string path, string validExtension)
224         {
225             Debug.Assert(!string.IsNullOrEmpty(path));
226             Debug.Assert(!string.IsNullOrEmpty(validExtension));
227
228             string extension = GetExtension(path);
229             if (!extension.Equals(validExtension, StringComparison.OrdinalIgnoreCase))
230             {
231                 throw EntityUtil.Metadata(Strings.InvalidFileExtension(path, extension, validExtension));
232             }
233         }
234
235         /// <summary>
236         /// Get paths to all artifacts, in the original, unexpanded form
237         /// </summary>
238         /// <returns>A List of strings identifying paths to all resources</returns>
239         public virtual List<string> GetOriginalPaths()
240         {
241             return new List<string>(new string[] { Path });
242         }
243
244         /// <summary>
245         /// Get paths to artifacts for a specific DataSpace, in the original, unexpanded
246         /// form
247         /// </summary>
248         /// <param name="spaceToGet">The DataSpace for the artifacts of interest</param>
249         /// <returns>A List of strings identifying paths to all artifacts for a specific DataSpace</returns>
250         public virtual List<string> GetOriginalPaths(DataSpace spaceToGet)
251         {
252             List<string> list = new List<string>();
253             if (MetadataArtifactLoader.IsArtifactOfDataSpace(Path, spaceToGet))
254             {
255                 list.Add(Path);
256             }
257             return list;
258         }
259
260         public virtual bool IsComposite 
261         {
262             get
263             {
264                 return false;
265             }
266         }
267         /// <summary>
268         /// Get paths to all artifacts
269         /// </summary>
270         /// <returns>A List of strings identifying paths to all resources</returns>
271         public abstract List<string> GetPaths();
272
273         /// <summary>
274         /// Get paths to artifacts for a specific DataSpace.
275         /// </summary>
276         /// <param name="spaceToGet">The DataSpace for the artifacts of interest</param>
277         /// <returns>A List of strings identifying paths to all artifacts for a specific DataSpace</returns>
278         public abstract List<string> GetPaths(DataSpace spaceToGet);
279
280         public List<XmlReader> GetReaders()
281         {
282             return GetReaders(null);
283         }
284         /// <summary>
285         /// Get XmlReaders for all resources
286         /// </summary>
287         /// <returns>A List of XmlReaders for all resources</returns>
288         public abstract List<XmlReader> GetReaders(Dictionary<MetadataArtifactLoader, XmlReader> sourceDictionary);
289
290         /// <summary>
291         /// Get XmlReaders for a specific DataSpace.
292         /// </summary>
293         /// <param name="spaceToGet">The DataSpace for the artifacts of interest</param>
294         /// <returns>A List of XmlReader object</returns>
295         public abstract List<XmlReader> CreateReaders(DataSpace spaceToGet);
296
297         /// <summary>
298         /// Helper method to determine whether a given path to a resource
299         /// starts with the "res://" prefix.
300         /// </summary>
301         /// <param name="path">The resource path to test.</param>
302         /// <returns>true if the path represents a resource location</returns>
303         internal static bool PathStartsWithResPrefix(string path)
304         {
305             return path.StartsWith(MetadataArtifactLoader.resPathPrefix, StringComparison.OrdinalIgnoreCase);
306         }
307
308         /// <summary>
309         /// Helper method to determine whether a resource identifies a C-Space
310         /// artifact.
311         /// </summary>
312         /// <param name="resource">The resource path</param>
313         /// <returns>true if the resource identifies a C-Space artifact</returns>
314         protected static bool IsCSpaceArtifact(string resource)
315         {
316             Debug.Assert(!string.IsNullOrEmpty(resource));
317
318             string extn = GetExtension(resource);
319             if (!string.IsNullOrEmpty(extn))
320             {
321                 return string.Compare(extn, XmlConstants.CSpaceSchemaExtension, StringComparison.OrdinalIgnoreCase) == 0;
322             }
323             return false;
324         }
325
326         /// <summary>
327         /// Helper method to determine whether a resource identifies an S-Space
328         /// artifact.
329         /// </summary>
330         /// <param name="resource">The resource path</param>
331         /// <returns>true if the resource identifies an S-Space artifact</returns>
332         protected static bool IsSSpaceArtifact(string resource)
333         {
334             Debug.Assert(!string.IsNullOrEmpty(resource));
335
336             string extn = GetExtension(resource);
337             if (!string.IsNullOrEmpty(extn))
338             {
339                 return string.Compare(extn, XmlConstants.SSpaceSchemaExtension, StringComparison.OrdinalIgnoreCase) == 0;
340             }
341             return false;
342         }
343
344         /// <summary>
345         /// Helper method to determine whether a resource identifies a CS-Space
346         /// artifact.
347         /// </summary>
348         /// <param name="resource">The resource path</param>
349         /// <returns>true if the resource identifies a CS-Space artifact</returns>
350         protected static bool IsCSSpaceArtifact(string resource)
351         {
352             Debug.Assert(!string.IsNullOrEmpty(resource));
353
354             string extn = GetExtension(resource);
355             if (!string.IsNullOrEmpty(extn))
356             {
357                 return string.Compare(extn, XmlConstants.CSSpaceSchemaExtension, StringComparison.OrdinalIgnoreCase) == 0;
358             }
359             return false;
360         }
361
362         // don't use Path.GetExtension because it is ok for the resource
363         // name to have characters in it that would be illegal in a path (ie '<' is illegal in a path)
364         // and when they do, Path.GetExtension throws and ArgumentException
365         private static string GetExtension(string resource)
366         {
367             if(String.IsNullOrEmpty(resource))
368                 return string.Empty;
369
370             int pos = resource.LastIndexOf('.');
371             if (pos < 0)
372                 return string.Empty;
373
374             return resource.Substring(pos);
375         }
376
377
378         /// <summary>
379         /// Helper method to determine whether a resource identifies a valid artifact.
380         /// </summary>
381         /// <param name="resource">The resource path</param>
382         /// <returns>true if the resource identifies a valid artifact</returns>
383         internal static bool IsValidArtifact(string resource)
384         {
385             Debug.Assert(!string.IsNullOrEmpty(resource));
386
387             string extn = GetExtension(resource);
388             if (!string.IsNullOrEmpty(extn))
389             {
390                 return (
391                     string.Compare(extn, XmlConstants.CSpaceSchemaExtension, StringComparison.OrdinalIgnoreCase) == 0 ||
392                     string.Compare(extn, XmlConstants.SSpaceSchemaExtension, StringComparison.OrdinalIgnoreCase) == 0 ||
393                     string.Compare(extn, XmlConstants.CSSpaceSchemaExtension, StringComparison.OrdinalIgnoreCase) == 0
394                 );
395             }
396             return false;
397         }
398
399         /// <summary>
400         /// This helper method accepts a resource URI and a value from the DataSpace enum
401         /// and determines whether the resource identifies an artifact of that DataSpace.
402         /// </summary>
403         /// <param name="resource">A URI to an artifact resource</param>
404         /// <param name="dataSpace">A DataSpace enum value</param>
405         /// <returns>true if the resource identifies an artifact of the specified DataSpace</returns>
406         protected static bool IsArtifactOfDataSpace(string resource, DataSpace dataSpace)
407         {
408             if (dataSpace == DataSpace.CSpace)
409                 return MetadataArtifactLoader.IsCSpaceArtifact(resource);
410
411             if (dataSpace == DataSpace.SSpace)
412                 return MetadataArtifactLoader.IsSSpaceArtifact(resource);
413
414             if (dataSpace == DataSpace.CSSpace)
415                 return MetadataArtifactLoader.IsCSSpaceArtifact(resource);
416
417             Debug.Assert(false, "Invalid DataSpace specified.");
418             return false;
419         }
420
421         /// <summary>
422         /// Normalize a file path:
423         ///     1. Add backslashes if given a drive letter.
424         ///     2. Resolve the '~' macro in a Web/ASP.NET environment.
425         ///     3. Expand the |DataDirectory| macro, if found in the argument.
426         ///     4. Convert relative paths into absolute paths.
427         /// </summary>
428         /// <param name="path">the path to normalize</param>
429         /// <returns>The normalized file path</returns>
430         [ResourceExposure(ResourceScope.Machine)] //Exposes the file name which is a Machine resource
431         [ResourceConsumption(ResourceScope.Machine)] //For Path.GetFullPath method call. But the path is not created in this method.
432         static internal string NormalizeFilePaths(string path)
433         {
434             bool getFullPath = true;    // used to determine whether we need to invoke GetFullPath()
435
436             if (!String.IsNullOrEmpty(path))
437             {
438                 path = path.Trim();
439
440                 // If the path starts with a '~' character, try to resolve it as a Web/ASP.NET
441                 // application path.
442                 //
443                 if (path.StartsWith(EdmConstants.WebHomeSymbol, StringComparison.Ordinal))
444                 {
445                     AspProxy aspProxy = new AspProxy();
446                     path = aspProxy.MapWebPath(path);
447                     getFullPath = false;
448                 }
449
450                 if (path.Length == 2 && path[1] == System.IO.Path.VolumeSeparatorChar)
451                 {
452                     path = path + System.IO.Path.DirectorySeparatorChar;
453                 }
454                 else
455                 {
456                     // See if the path contains the |DataDirectory| macro that we need to
457                     // expand. Note that ExpandDataDirectory() won't process the path unless
458                     // it begins with the macro.
459                     //
460                     string fullPath = System.Data.EntityClient.DbConnectionOptions.ExpandDataDirectory(
461                             System.Data.EntityClient.EntityConnectionStringBuilder.MetadataParameterName,   // keyword ("Metadata")
462                             path                                                                            // value
463                         );
464
465                     // ExpandDataDirectory() returns null if it doesn't find the macro in its
466                     // argument.
467                     //
468                     if (fullPath != null)
469                     {
470                         path = fullPath;
471                         getFullPath = false;
472                     }
473                 }
474             }
475             try
476             {
477                 if (getFullPath)
478                 {
479                     path = System.IO.Path.GetFullPath(path);
480                 }
481             }
482             catch (ArgumentException e)
483             {
484                 throw EntityUtil.Metadata(System.Data.Entity.Strings.NotValidInputPath, e);
485             }
486             catch (NotSupportedException e)
487             {
488                 throw EntityUtil.Metadata(System.Data.Entity.Strings.NotValidInputPath, e);
489             }
490             catch (PathTooLongException)
491             {
492                 throw EntityUtil.Metadata(System.Data.Entity.Strings.NotValidInputPath);
493             }
494
495             return path;
496         }
497
498
499     }
500 }