1 //---------------------------------------------------------------------
2 // <copyright file="MetadataArtifactLoader.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System
.Data
.Metadata
.Edm
12 using System
.Collections
.Generic
;
13 using System
.Data
.Entity
;
14 using System
.Diagnostics
;
16 using System
.Runtime
.Versioning
;
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.
24 internal abstract class MetadataArtifactLoader
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
= @"*";
32 /// Read-only access to the resource/file path
34 public abstract string Path{ get; }
35 public abstract void CollectFilePermissionPaths(List
<string> paths
, DataSpace spaceToGet
);
38 /// This enum is used to indicate the level of extension check to be perfoemed
39 /// on a metadata URI.
41 public enum ExtensionCheck
44 /// Do not perform any extension check
49 /// Check the extension against a specific value
54 /// Check the extension against the set of acceptable extensions
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
)
66 return Create(path
, extensionCheck
, validExtension
, uriRegistry
, new DefaultAssemblyResolver());
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.
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
)
87 Debug
.Assert(path
!= null);
88 Debug
.Assert(resolver
!= null);
90 // res:// -based artifacts
92 if (MetadataArtifactLoader
.PathStartsWithResPrefix(path
))
94 return MetadataArtifactLoaderCompositeResource
.CreateResourceLoader(path
, extensionCheck
, validExtension
, uriRegistry
, resolver
);
99 string normalizedPath
= MetadataArtifactLoader
.NormalizeFilePaths(path
);
100 if (Directory
.Exists(normalizedPath
))
102 return new MetadataArtifactLoaderCompositeFile(normalizedPath
, uriRegistry
);
104 else if (File
.Exists(normalizedPath
))
106 switch (extensionCheck
)
108 case ExtensionCheck
.Specific
:
109 CheckArtifactExtension(normalizedPath
, validExtension
);
112 case ExtensionCheck
.All
:
113 if (!MetadataArtifactLoader
.IsValidArtifact(normalizedPath
))
115 throw EntityUtil
.Metadata(Strings
.InvalidMetadataPath
);
120 return new MetadataArtifactLoaderFile(normalizedPath
, uriRegistry
);
123 throw EntityUtil
.Metadata(Strings
.InvalidMetadataPath
);
127 /// Factory method to create an aggregating artifact loader, one that encapsulates
128 /// multiple collections.
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
)
134 return new MetadataArtifactLoaderComposite(allCollections
);
138 /// Helper method that wraps a list of file paths in MetadataArtifactLoader instances.
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
)
147 Debug
.Assert(!string.IsNullOrEmpty(validExtension
));
149 return CreateCompositeFromFilePaths(filePaths
, validExtension
, new DefaultAssemblyResolver());
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
)
156 ExtensionCheck extensionCheck
;
157 if (string.IsNullOrEmpty(validExtension
))
159 extensionCheck
= ExtensionCheck
.All
;
163 extensionCheck
= ExtensionCheck
.Specific
;
166 List
<MetadataArtifactLoader
> loaders
= new List
<MetadataArtifactLoader
>();
168 // The following set is used to remove duplicate paths from the incoming array
169 HashSet
<string> uriRegistry
= new HashSet
<string>(StringComparer
.OrdinalIgnoreCase
);
171 foreach(string path
in filePaths
)
173 if (string.IsNullOrEmpty(path
))
175 throw EntityUtil
.Metadata(System
.Data
.Entity
.Strings
.NotValidInputPath
,
176 EntityUtil
.CollectionParameterElementIsNullOrEmpty("filePaths"));
179 string trimedPath
= path
.Trim();
180 if (trimedPath
.Length
> 0)
182 loaders
.Add(MetadataArtifactLoader
.Create(
192 return MetadataArtifactLoader
.Create(loaders
);
196 /// Helper method that wraps a collection of XmlReader objects in MetadataArtifactLoader
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
)
203 List
<MetadataArtifactLoader
> loaders
= new List
<MetadataArtifactLoader
>();
205 foreach (XmlReader reader
in xmlReaders
)
209 throw EntityUtil
.CollectionParameterElementIsNull("xmlReaders");
212 loaders
.Add(new MetadataArtifactLoaderXmlReaderWrapper(reader
));
215 return MetadataArtifactLoader
.Create(loaders
);
219 /// If the path doesn't have the right extension, throw
221 /// <param name="path">The path to the resource</param>
222 /// <param name="validExtension"></param>
223 internal static void CheckArtifactExtension(string path
, string validExtension
)
225 Debug
.Assert(!string.IsNullOrEmpty(path
));
226 Debug
.Assert(!string.IsNullOrEmpty(validExtension
));
228 string extension
= GetExtension(path
);
229 if (!extension
.Equals(validExtension
, StringComparison
.OrdinalIgnoreCase
))
231 throw EntityUtil
.Metadata(Strings
.InvalidFileExtension(path
, extension
, validExtension
));
236 /// Get paths to all artifacts, in the original, unexpanded form
238 /// <returns>A List of strings identifying paths to all resources</returns>
239 public virtual List
<string> GetOriginalPaths()
241 return new List
<string>(new string[] { Path }
);
245 /// Get paths to artifacts for a specific DataSpace, in the original, unexpanded
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
)
252 List
<string> list
= new List
<string>();
253 if (MetadataArtifactLoader
.IsArtifactOfDataSpace(Path
, spaceToGet
))
260 public virtual bool IsComposite
268 /// Get paths to all artifacts
270 /// <returns>A List of strings identifying paths to all resources</returns>
271 public abstract List
<string> GetPaths();
274 /// Get paths to artifacts for a specific DataSpace.
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
);
280 public List
<XmlReader
> GetReaders()
282 return GetReaders(null);
285 /// Get XmlReaders for all resources
287 /// <returns>A List of XmlReaders for all resources</returns>
288 public abstract List
<XmlReader
> GetReaders(Dictionary
<MetadataArtifactLoader
, XmlReader
> sourceDictionary
);
291 /// Get XmlReaders for a specific DataSpace.
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
);
298 /// Helper method to determine whether a given path to a resource
299 /// starts with the "res://" prefix.
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
)
305 return path
.StartsWith(MetadataArtifactLoader
.resPathPrefix
, StringComparison
.OrdinalIgnoreCase
);
309 /// Helper method to determine whether a resource identifies a C-Space
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
)
316 Debug
.Assert(!string.IsNullOrEmpty(resource
));
318 string extn
= GetExtension(resource
);
319 if (!string.IsNullOrEmpty(extn
))
321 return string.Compare(extn
, XmlConstants
.CSpaceSchemaExtension
, StringComparison
.OrdinalIgnoreCase
) == 0;
327 /// Helper method to determine whether a resource identifies an S-Space
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
)
334 Debug
.Assert(!string.IsNullOrEmpty(resource
));
336 string extn
= GetExtension(resource
);
337 if (!string.IsNullOrEmpty(extn
))
339 return string.Compare(extn
, XmlConstants
.SSpaceSchemaExtension
, StringComparison
.OrdinalIgnoreCase
) == 0;
345 /// Helper method to determine whether a resource identifies a CS-Space
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
)
352 Debug
.Assert(!string.IsNullOrEmpty(resource
));
354 string extn
= GetExtension(resource
);
355 if (!string.IsNullOrEmpty(extn
))
357 return string.Compare(extn
, XmlConstants
.CSSpaceSchemaExtension
, StringComparison
.OrdinalIgnoreCase
) == 0;
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
)
367 if(String
.IsNullOrEmpty(resource
))
370 int pos
= resource
.LastIndexOf('.');
374 return resource
.Substring(pos
);
379 /// Helper method to determine whether a resource identifies a valid artifact.
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
)
385 Debug
.Assert(!string.IsNullOrEmpty(resource
));
387 string extn
= GetExtension(resource
);
388 if (!string.IsNullOrEmpty(extn
))
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
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.
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
)
408 if (dataSpace
== DataSpace
.CSpace
)
409 return MetadataArtifactLoader
.IsCSpaceArtifact(resource
);
411 if (dataSpace
== DataSpace
.SSpace
)
412 return MetadataArtifactLoader
.IsSSpaceArtifact(resource
);
414 if (dataSpace
== DataSpace
.CSSpace
)
415 return MetadataArtifactLoader
.IsCSSpaceArtifact(resource
);
417 Debug
.Assert(false, "Invalid DataSpace specified.");
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.
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
)
434 bool getFullPath
= true; // used to determine whether we need to invoke GetFullPath()
436 if (!String
.IsNullOrEmpty(path
))
440 // If the path starts with a '~' character, try to resolve it as a Web/ASP.NET
443 if (path
.StartsWith(EdmConstants
.WebHomeSymbol
, StringComparison
.Ordinal
))
445 AspProxy aspProxy
= new AspProxy();
446 path
= aspProxy
.MapWebPath(path
);
450 if (path
.Length
== 2 && path
[1] == System
.IO
.Path
.VolumeSeparatorChar
)
452 path
= path
+ System
.IO
.Path
.DirectorySeparatorChar
;
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.
460 string fullPath
= System
.Data
.EntityClient
.DbConnectionOptions
.ExpandDataDirectory(
461 System
.Data
.EntityClient
.EntityConnectionStringBuilder
.MetadataParameterName
, // keyword ("Metadata")
465 // ExpandDataDirectory() returns null if it doesn't find the macro in its
468 if (fullPath
!= null)
479 path
= System
.IO
.Path
.GetFullPath(path
);
482 catch (ArgumentException e
)
484 throw EntityUtil
.Metadata(System
.Data
.Entity
.Strings
.NotValidInputPath
, e
);
486 catch (NotSupportedException e
)
488 throw EntityUtil
.Metadata(System
.Data
.Entity
.Strings
.NotValidInputPath
, e
);
490 catch (PathTooLongException
)
492 throw EntityUtil
.Metadata(System
.Data
.Entity
.Strings
.NotValidInputPath
);