//---------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Metadata.Edm
{
using System.Collections.Generic;
using System.Data.Entity;
using System.Diagnostics;
using System.IO;
using System.Runtime.Versioning;
using System.Xml;
///
/// This is the base class for the resource metadata artifact loader; derived
/// classes enacpsulate a single resource as well as collections of resources,
/// along the lines of the Composite pattern.
///
internal abstract class MetadataArtifactLoader
{
protected readonly static string resPathPrefix = @"res://";
protected readonly static string resPathSeparator = @"/";
protected readonly static string altPathSeparator = @"\";
protected readonly static string wildcard = @"*";
///
/// Read-only access to the resource/file path
///
public abstract string Path{ get; }
public abstract void CollectFilePermissionPaths(List paths, DataSpace spaceToGet);
///
/// This enum is used to indicate the level of extension check to be perfoemed
/// on a metadata URI.
///
public enum ExtensionCheck
{
///
/// Do not perform any extension check
///
None = 0,
///
/// Check the extension against a specific value
///
Specific,
///
/// Check the extension against the set of acceptable extensions
///
All
}
[ResourceExposure(ResourceScope.Machine)] //Exposes the file name which is a Machine resource
[ResourceConsumption(ResourceScope.Machine)] //For Create method call. But the path is not created in this method.
public static MetadataArtifactLoader Create(string path,
ExtensionCheck extensionCheck,
string validExtension,
ICollection uriRegistry)
{
return Create(path, extensionCheck, validExtension, uriRegistry, new DefaultAssemblyResolver());
}
///
/// Factory method to create an artifact loader. This is where an appropriate
/// subclass of MetadataArtifactLoader is created, depending on the kind of
/// resource it will encapsulate.
///
/// The path to the resource(s) to be loaded
/// Any URI extension checks to perform
/// A specific extension for an artifact resource
/// The global registry of URIs
///
/// A concrete instance of an artifact loader.
[ResourceExposure(ResourceScope.Machine)] //Exposes the file name which is a Machine resource
[ResourceConsumption(ResourceScope.Machine)] //For CheckArtifactExtension method call. But the path is not created in this method.
internal static MetadataArtifactLoader Create(string path,
ExtensionCheck extensionCheck,
string validExtension,
ICollection uriRegistry,
MetadataArtifactAssemblyResolver resolver)
{
Debug.Assert(path != null);
Debug.Assert(resolver != null);
// res:// -based artifacts
//
if (MetadataArtifactLoader.PathStartsWithResPrefix(path))
{
return MetadataArtifactLoaderCompositeResource.CreateResourceLoader(path, extensionCheck, validExtension, uriRegistry, resolver);
}
// Files and Folders
//
string normalizedPath = MetadataArtifactLoader.NormalizeFilePaths(path);
if (Directory.Exists(normalizedPath))
{
return new MetadataArtifactLoaderCompositeFile(normalizedPath, uriRegistry);
}
else if (File.Exists(normalizedPath))
{
switch (extensionCheck)
{
case ExtensionCheck.Specific:
CheckArtifactExtension(normalizedPath, validExtension);
break;
case ExtensionCheck.All:
if (!MetadataArtifactLoader.IsValidArtifact(normalizedPath))
{
throw EntityUtil.Metadata(Strings.InvalidMetadataPath);
}
break;
}
return new MetadataArtifactLoaderFile(normalizedPath, uriRegistry);
}
throw EntityUtil.Metadata(Strings.InvalidMetadataPath);
}
///
/// Factory method to create an aggregating artifact loader, one that encapsulates
/// multiple collections.
///
/// The list of collections to be aggregated
/// A concrete instance of an artifact loader.
public static MetadataArtifactLoader Create(List allCollections)
{
return new MetadataArtifactLoaderComposite(allCollections);
}
///
/// Helper method that wraps a list of file paths in MetadataArtifactLoader instances.
///
/// The list of file paths to wrap
/// An acceptable extension for the file
/// An instance of MetadataArtifactLoader
[ResourceExposure(ResourceScope.Machine)] //Exposes the file names which are a Machine resource
[ResourceConsumption(ResourceScope.Machine)] //For CreateCompositeFromFilePaths method call. But the path is not created in this method.
public static MetadataArtifactLoader CreateCompositeFromFilePaths(IEnumerable filePaths, string validExtension)
{
Debug.Assert(!string.IsNullOrEmpty(validExtension));
return CreateCompositeFromFilePaths(filePaths, validExtension, new DefaultAssemblyResolver());
}
[ResourceExposure(ResourceScope.Machine)] //Exposes the file names which are a Machine resource
[ResourceConsumption(ResourceScope.Machine)] //For Create method call. But the paths are not created in this method.
internal static MetadataArtifactLoader CreateCompositeFromFilePaths(IEnumerable filePaths, string validExtension, MetadataArtifactAssemblyResolver resolver)
{
ExtensionCheck extensionCheck;
if (string.IsNullOrEmpty(validExtension))
{
extensionCheck = ExtensionCheck.All;
}
else
{
extensionCheck = ExtensionCheck.Specific;
}
List loaders = new List();
// The following set is used to remove duplicate paths from the incoming array
HashSet uriRegistry = new HashSet(StringComparer.OrdinalIgnoreCase);
foreach(string path in filePaths)
{
if (string.IsNullOrEmpty(path))
{
throw EntityUtil.Metadata(System.Data.Entity.Strings.NotValidInputPath,
EntityUtil.CollectionParameterElementIsNullOrEmpty("filePaths"));
}
string trimedPath = path.Trim();
if (trimedPath.Length > 0)
{
loaders.Add(MetadataArtifactLoader.Create(
trimedPath,
extensionCheck,
validExtension,
uriRegistry,
resolver)
);
}
}
return MetadataArtifactLoader.Create(loaders);
}
///
/// Helper method that wraps a collection of XmlReader objects in MetadataArtifactLoader
/// instances.
///
/// The collection of XmlReader objects to wrap
/// An instance of MetadataArtifactLoader
public static MetadataArtifactLoader CreateCompositeFromXmlReaders(IEnumerable xmlReaders)
{
List loaders = new List();
foreach (XmlReader reader in xmlReaders)
{
if (reader == null)
{
throw EntityUtil.CollectionParameterElementIsNull("xmlReaders");
}
loaders.Add(new MetadataArtifactLoaderXmlReaderWrapper(reader));
}
return MetadataArtifactLoader.Create(loaders);
}
///
/// If the path doesn't have the right extension, throw
///
/// The path to the resource
///
internal static void CheckArtifactExtension(string path, string validExtension)
{
Debug.Assert(!string.IsNullOrEmpty(path));
Debug.Assert(!string.IsNullOrEmpty(validExtension));
string extension = GetExtension(path);
if (!extension.Equals(validExtension, StringComparison.OrdinalIgnoreCase))
{
throw EntityUtil.Metadata(Strings.InvalidFileExtension(path, extension, validExtension));
}
}
///
/// Get paths to all artifacts, in the original, unexpanded form
///
/// A List of strings identifying paths to all resources
public virtual List GetOriginalPaths()
{
return new List(new string[] { Path });
}
///
/// Get paths to artifacts for a specific DataSpace, in the original, unexpanded
/// form
///
/// The DataSpace for the artifacts of interest
/// A List of strings identifying paths to all artifacts for a specific DataSpace
public virtual List GetOriginalPaths(DataSpace spaceToGet)
{
List list = new List();
if (MetadataArtifactLoader.IsArtifactOfDataSpace(Path, spaceToGet))
{
list.Add(Path);
}
return list;
}
public virtual bool IsComposite
{
get
{
return false;
}
}
///
/// Get paths to all artifacts
///
/// A List of strings identifying paths to all resources
public abstract List GetPaths();
///
/// Get paths to artifacts for a specific DataSpace.
///
/// The DataSpace for the artifacts of interest
/// A List of strings identifying paths to all artifacts for a specific DataSpace
public abstract List GetPaths(DataSpace spaceToGet);
public List GetReaders()
{
return GetReaders(null);
}
///
/// Get XmlReaders for all resources
///
/// A List of XmlReaders for all resources
public abstract List GetReaders(Dictionary sourceDictionary);
///
/// Get XmlReaders for a specific DataSpace.
///
/// The DataSpace for the artifacts of interest
/// A List of XmlReader object
public abstract List CreateReaders(DataSpace spaceToGet);
///
/// Helper method to determine whether a given path to a resource
/// starts with the "res://" prefix.
///
/// The resource path to test.
/// true if the path represents a resource location
internal static bool PathStartsWithResPrefix(string path)
{
return path.StartsWith(MetadataArtifactLoader.resPathPrefix, StringComparison.OrdinalIgnoreCase);
}
///
/// Helper method to determine whether a resource identifies a C-Space
/// artifact.
///
/// The resource path
/// true if the resource identifies a C-Space artifact
protected static bool IsCSpaceArtifact(string resource)
{
Debug.Assert(!string.IsNullOrEmpty(resource));
string extn = GetExtension(resource);
if (!string.IsNullOrEmpty(extn))
{
return string.Compare(extn, XmlConstants.CSpaceSchemaExtension, StringComparison.OrdinalIgnoreCase) == 0;
}
return false;
}
///
/// Helper method to determine whether a resource identifies an S-Space
/// artifact.
///
/// The resource path
/// true if the resource identifies an S-Space artifact
protected static bool IsSSpaceArtifact(string resource)
{
Debug.Assert(!string.IsNullOrEmpty(resource));
string extn = GetExtension(resource);
if (!string.IsNullOrEmpty(extn))
{
return string.Compare(extn, XmlConstants.SSpaceSchemaExtension, StringComparison.OrdinalIgnoreCase) == 0;
}
return false;
}
///
/// Helper method to determine whether a resource identifies a CS-Space
/// artifact.
///
/// The resource path
/// true if the resource identifies a CS-Space artifact
protected static bool IsCSSpaceArtifact(string resource)
{
Debug.Assert(!string.IsNullOrEmpty(resource));
string extn = GetExtension(resource);
if (!string.IsNullOrEmpty(extn))
{
return string.Compare(extn, XmlConstants.CSSpaceSchemaExtension, StringComparison.OrdinalIgnoreCase) == 0;
}
return false;
}
// don't use Path.GetExtension because it is ok for the resource
// name to have characters in it that would be illegal in a path (ie '<' is illegal in a path)
// and when they do, Path.GetExtension throws and ArgumentException
private static string GetExtension(string resource)
{
if(String.IsNullOrEmpty(resource))
return string.Empty;
int pos = resource.LastIndexOf('.');
if (pos < 0)
return string.Empty;
return resource.Substring(pos);
}
///
/// Helper method to determine whether a resource identifies a valid artifact.
///
/// The resource path
/// true if the resource identifies a valid artifact
internal static bool IsValidArtifact(string resource)
{
Debug.Assert(!string.IsNullOrEmpty(resource));
string extn = GetExtension(resource);
if (!string.IsNullOrEmpty(extn))
{
return (
string.Compare(extn, XmlConstants.CSpaceSchemaExtension, StringComparison.OrdinalIgnoreCase) == 0 ||
string.Compare(extn, XmlConstants.SSpaceSchemaExtension, StringComparison.OrdinalIgnoreCase) == 0 ||
string.Compare(extn, XmlConstants.CSSpaceSchemaExtension, StringComparison.OrdinalIgnoreCase) == 0
);
}
return false;
}
///
/// This helper method accepts a resource URI and a value from the DataSpace enum
/// and determines whether the resource identifies an artifact of that DataSpace.
///
/// A URI to an artifact resource
/// A DataSpace enum value
/// true if the resource identifies an artifact of the specified DataSpace
protected static bool IsArtifactOfDataSpace(string resource, DataSpace dataSpace)
{
if (dataSpace == DataSpace.CSpace)
return MetadataArtifactLoader.IsCSpaceArtifact(resource);
if (dataSpace == DataSpace.SSpace)
return MetadataArtifactLoader.IsSSpaceArtifact(resource);
if (dataSpace == DataSpace.CSSpace)
return MetadataArtifactLoader.IsCSSpaceArtifact(resource);
Debug.Assert(false, "Invalid DataSpace specified.");
return false;
}
///
/// Normalize a file path:
/// 1. Add backslashes if given a drive letter.
/// 2. Resolve the '~' macro in a Web/ASP.NET environment.
/// 3. Expand the |DataDirectory| macro, if found in the argument.
/// 4. Convert relative paths into absolute paths.
///
/// the path to normalize
/// The normalized file path
[ResourceExposure(ResourceScope.Machine)] //Exposes the file name which is a Machine resource
[ResourceConsumption(ResourceScope.Machine)] //For Path.GetFullPath method call. But the path is not created in this method.
static internal string NormalizeFilePaths(string path)
{
bool getFullPath = true; // used to determine whether we need to invoke GetFullPath()
if (!String.IsNullOrEmpty(path))
{
path = path.Trim();
// If the path starts with a '~' character, try to resolve it as a Web/ASP.NET
// application path.
//
if (path.StartsWith(EdmConstants.WebHomeSymbol, StringComparison.Ordinal))
{
AspProxy aspProxy = new AspProxy();
path = aspProxy.MapWebPath(path);
getFullPath = false;
}
if (path.Length == 2 && path[1] == System.IO.Path.VolumeSeparatorChar)
{
path = path + System.IO.Path.DirectorySeparatorChar;
}
else
{
// See if the path contains the |DataDirectory| macro that we need to
// expand. Note that ExpandDataDirectory() won't process the path unless
// it begins with the macro.
//
string fullPath = System.Data.EntityClient.DbConnectionOptions.ExpandDataDirectory(
System.Data.EntityClient.EntityConnectionStringBuilder.MetadataParameterName, // keyword ("Metadata")
path // value
);
// ExpandDataDirectory() returns null if it doesn't find the macro in its
// argument.
//
if (fullPath != null)
{
path = fullPath;
getFullPath = false;
}
}
}
try
{
if (getFullPath)
{
path = System.IO.Path.GetFullPath(path);
}
}
catch (ArgumentException e)
{
throw EntityUtil.Metadata(System.Data.Entity.Strings.NotValidInputPath, e);
}
catch (NotSupportedException e)
{
throw EntityUtil.Metadata(System.Data.Entity.Strings.NotValidInputPath, e);
}
catch (PathTooLongException)
{
throw EntityUtil.Metadata(System.Data.Entity.Strings.NotValidInputPath);
}
return path;
}
}
}