User management support classes (#1856)

* WIP

* Fix nullable warnings in UserData class

* WIP2

* WIP

* Refresh database prototype classes

* Fix some typos & merge issues

* WIP

* WIP

* WIP

* Additional updates

* Remove unneded using
This commit is contained in:
Karl Burtram
2023-02-08 18:02:08 -08:00
committed by GitHub
parent ee086e2067
commit 2ef5f0918a
24 changed files with 3584 additions and 1336 deletions

View File

@@ -3,11 +3,11 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Security;
@@ -30,7 +30,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
{
SQL,
OLAP, //This type is used only for non-express sku
SQLCE,
SQLCE,
UNKNOWN
}
@@ -38,32 +38,31 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
#region Fields
private ServerConnection serverConnection;
private Server m_server = null;
protected XmlDocument m_doc = null;
private XmlDocument originalDocument = null;
private SqlOlapConnectionInfoBase connectionInfo = null;
private SqlConnectionInfoWithConnection sqlCiWithConnection;
private ServerConnection? serverConnection;
private Server? m_server;
protected XmlDocument? m_doc;
private XmlDocument? originalDocument;
private SqlOlapConnectionInfoBase? connectionInfo;
private SqlConnectionInfoWithConnection? sqlCiWithConnection;
private bool ownConnection = true;
private IManagedConnection managedConnection;
protected string serverName;
private IManagedConnection? managedConnection;
protected string? serverName;
//This member is used for non-express sku only
protected string olapServerName;
protected string? olapServerName;
protected string sqlceFilename;
protected string? sqlceFilename;
private ServerType serverType = ServerType.UNKNOWN;
private Hashtable m_hashTable = null;
private Hashtable? m_hashTable;
private string objectNameKey = "object-name-9524b5c1-e996-4119-a433-b5b947985566";
private string objectSchemaKey = "object-schema-ccaf2efe-8fa3-4f62-be79-62ef3cbe7390";
private SqlSmoObject sqlDialogSubject = null;
private SqlSmoObject? sqlDialogSubject;
private int sqlServerVersion = 0;
private int sqlServerEffectiveVersion = 0;
#endregion
@@ -73,7 +72,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <summary>
/// gets/sets XmlDocument with parameters
/// </summary>
public XmlDocument Document
public XmlDocument? Document
{
get
{
@@ -85,8 +84,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
if (value != null)
{
//this.originalDocument = (XmlDocument) value.Clone();
this.originalDocument = value;
this.originalDocument = (XmlDocument)value.Clone();
}
else
{
@@ -111,7 +109,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <summary>
/// gets/sets SMO server object
/// </summary>
public Server Server
public Server? Server
{
get
{
@@ -127,26 +125,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <summary>
/// connection info that should be used by the dialogs
/// </summary>
public SqlOlapConnectionInfoBase ConnectionInfo
public SqlOlapConnectionInfoBase? ConnectionInfo
{
get
{
//// update the database name in the serverconnection object to set the correct database context when connected to Azure
//var conn = this.connectionInfo as SqlConnectionInfoWithConnection;
// update the database name in the serverconnection object to set the correct database context when connected to Azure
var conn = this.connectionInfo as SqlConnectionInfoWithConnection;
//if (conn != null && conn.ServerConnection.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase)
//{
// if (this.RelevantDatabaseName != null)
// {
// IComparer<string> dbNamesComparer = ServerConnection.ConnectionFactory.GetInstance(conn.ServerConnection).ServerComparer as IComparer<string>;
// if (dbNamesComparer.Compare(this.RelevantDatabaseName, conn.DatabaseName) != 0)
// {
// ServerConnection serverConnection = conn.ServerConnection.GetDatabaseConnection(this.RelevantDatabaseName, true, conn.AccessToken);
// ((SqlConnectionInfoWithConnection)this.connectionInfo).ServerConnection = serverConnection;
// }
// }
//}
// Don't update the database name if this is a Gen3 connection since Gen3 supports USE from the server connection.
if (conn != null &&
conn.ServerConnection.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase &&
!(conn.ServerConnection.DatabaseEngineEdition == DatabaseEngineEdition.SqlDataWarehouse &&
conn.ServerConnection.ProductVersion.Major >= 12))
{
if (this.RelevantDatabaseName != null)
{
IComparer<string> dbNamesComparer = new ServerComparer(conn.ServerConnection, "master");
if (dbNamesComparer.Compare(this.RelevantDatabaseName, conn.DatabaseName) != 0 && this.connectionInfo != null)
{
ServerConnection databaseConnection = conn.ServerConnection.GetDatabaseConnection(this.RelevantDatabaseName, true, conn.AccessToken);
((SqlConnectionInfoWithConnection)this.connectionInfo).ServerConnection = databaseConnection;
}
}
}
return this.connectionInfo;
}
}
@@ -163,11 +164,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
{
if (this.serverType != ServerType.SQL)
{
System.Diagnostics.Debug.Assert(false, "CDataContainer.ServerConnection can be used only for SQL connection");
throw new InvalidOperationException();
}
if (this.connectionInfo == null)
{
System.Diagnostics.Debug.Assert(false, "CDataContainer.ServerConnection can be used only after ConnectionInfo property has been set");
throw new InvalidOperationException();
}
@@ -177,12 +182,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
else
{
SqlConnectionInfo sci = this.connectionInfo as SqlConnectionInfo;
SqlConnectionInfo? sci = this.connectionInfo as SqlConnectionInfo;
System.Diagnostics.Debug.Assert(sci != null, "CDataContainer.ServerConnection: connection info MUST be SqlConnectionInfo");
this.serverConnection = new ServerConnection(sci);
}
}
System.Diagnostics.Debug.Assert(this.serverConnection != null);
return this.serverConnection;
}
}
@@ -191,7 +197,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// returns SMO server connection object constructed off the connectionInfo.
/// This method cannot work until ConnectionInfo property has been set
/// </summary>
public SqlConnectionInfoWithConnection SqlInfoWithConnection
public SqlConnectionInfoWithConnection? SqlInfoWithConnection
{
get
{
@@ -199,11 +205,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
{
if (this.serverType != ServerType.SQL)
{
System.Diagnostics.Debug.Assert(false, "CDataContainer.ServerConnection can be used only for SQL connection");
throw new InvalidOperationException();
}
if (this.connectionInfo == null)
{
System.Diagnostics.Debug.Assert(false, "CDataContainer.ServerConnection can be used only after ConnectionInfo property has been set");
throw new InvalidOperationException();
}
@@ -214,16 +224,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
else
{
SqlConnectionInfo sci = this.connectionInfo as SqlConnectionInfo;
SqlConnectionInfo? sci = this.connectionInfo as SqlConnectionInfo;
System.Diagnostics.Debug.Assert(sci != null, "CDataContainer.ServerConnection: connection info MUST be SqlConnectionInfo");
this.serverConnection = new ServerConnection(sci);
}
}
System.Diagnostics.Debug.Assert(this.serverConnection != null);
return this.sqlCiWithConnection;
}
}
public string ServerName
public string? ServerName
{
get
{
@@ -247,7 +259,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
}
public string SqlCeFileName
public string? SqlCeFileName
{
get
{
@@ -260,7 +272,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
//This member is used for non-express sku only
public string OlapServerName
public string? OlapServerName
{
get
{
@@ -418,11 +430,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <summary>
/// The SQL SMO object that is the subject of the dialog.
/// </summary>
public SqlSmoObject SqlDialogSubject
public SqlSmoObject? SqlDialogSubject
{
get
{
SqlSmoObject result = null;
SqlSmoObject? result;
if (this.sqlDialogSubject != null)
{
@@ -430,7 +442,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
else
{
result = this.Server.GetSmoObject(this.ObjectUrn);
result = this.Server?.GetSmoObject(this.ObjectUrn);
}
return result;
@@ -451,6 +463,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
{
bool result = false;
System.Diagnostics.Debug.Assert(this.Server != null, "SMO Server object is null!");
System.Diagnostics.Debug.Assert(this.Server.ConnectionContext != null, "SMO Server Connection object is null!");
if (this.Server != null && this.Server.ConnectionContext != null)
{
result = this.Server.ConnectionContext.IsInFixedServerRole(FixedServerRoles.SysAdmin);
@@ -471,6 +486,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
string result = String.Empty;
string urnText = this.GetDocumentPropertyString("urn");
System.Diagnostics.Debug.Assert(urnText.Length != 0, "couldn't get relevant URN");
if (urnText.Length != 0)
{
Urn urn = new Urn(urnText);
@@ -501,6 +518,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
{
this.sqlServerVersion = 9;
System.Diagnostics.Debug.Assert(this.ConnectionInfo != null, "ConnectionInfo is null!");
System.Diagnostics.Debug.Assert(ServerType.SQL == this.ContainerServerType, "unexpected server type");
if ((this.ConnectionInfo != null) && (ServerType.SQL == this.ContainerServerType))
{
Enumerator enumerator = new Enumerator();
@@ -520,99 +540,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
/// <summary>
/// The server version the database is emulating. If database compatibility level is
/// not relevant to the subject, then this just returns the actual server version.
/// </summary>
public int EffectiveSqlServerVersion
{
get
{
if (this.sqlServerEffectiveVersion == 0)
{
this.sqlServerEffectiveVersion = 9;
if ((this.ConnectionInfo != null) && (ServerType.SQL == this.ContainerServerType))
{
string databaseName = this.RelevantDatabaseName;
if (databaseName.Length != 0)
{
Enumerator enumerator = new Enumerator();
Urn urn = String.Format("Server/Database[@Name='{0}']", Urn.EscapeString(databaseName));
string[] fields = new string[] { "CompatibilityLevel" };
DataTable dataTable = enumerator.Process(this.ConnectionInfo, new Request(urn, fields));
if (dataTable.Rows.Count != 0)
{
CompatibilityLevel level = (CompatibilityLevel)dataTable.Rows[0][0];
switch (level)
{
case CompatibilityLevel.Version60:
case CompatibilityLevel.Version65:
this.sqlServerEffectiveVersion = 6;
break;
case CompatibilityLevel.Version70:
this.sqlServerEffectiveVersion = 7;
break;
case CompatibilityLevel.Version80:
this.sqlServerEffectiveVersion = 8;
break;
case CompatibilityLevel.Version90:
this.sqlServerEffectiveVersion = 9;
break;
case CompatibilityLevel.Version100:
this.sqlServerEffectiveVersion = 10;
break;
case CompatibilityLevel.Version110:
this.sqlServerEffectiveVersion = 11;
break;
case CompatibilityLevel.Version120:
this.sqlServerEffectiveVersion = 12;
break;
case CompatibilityLevel.Version130:
this.sqlServerEffectiveVersion = 13;
break;
case CompatibilityLevel.Version140:
this.sqlServerEffectiveVersion = 14;
break;
default:
this.sqlServerEffectiveVersion = 14;
break;
}
}
else
{
this.sqlServerEffectiveVersion = this.SqlServerVersion;
}
}
else
{
this.sqlServerEffectiveVersion = this.SqlServerVersion;
}
}
}
return this.sqlServerEffectiveVersion;
}
}
#endregion
#region Constructors, finalizer
@@ -628,9 +555,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <param name="ciObj">connection info containing live connection</param>
public CDataContainer(object ciObj, bool ownConnection)
{
SqlConnectionInfoWithConnection ci = (SqlConnectionInfoWithConnection)ciObj;
SqlConnectionInfoWithConnection ci = (SqlConnectionInfoWithConnection)ciObj;
if (ci == null)
{
System.Diagnostics.Debug.Assert(false, "CDataContainer.CDataContainer(SqlConnectionInfoWithConnection): specified connection info is null");
throw new ArgumentNullException("ci");
}
ApplyConnectionInfo(ci, ownConnection);
@@ -645,9 +574,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <param name="ci">connection info containing live connection</param>
public CDataContainer(ServerType serverType, object ciObj, bool ownConnection)
{
SqlConnectionInfoWithConnection ci = (SqlConnectionInfoWithConnection)ciObj;
SqlConnectionInfoWithConnection ci = (SqlConnectionInfoWithConnection)ciObj;
if (ci == null)
{
System.Diagnostics.Debug.Assert(false, "CDataContainer.CDataContainer(SqlConnectionInfoWithConnection): specified connection info is null");
throw new ArgumentNullException("ci");
}
@@ -656,12 +587,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
if (serverType == ServerType.SQL)
{
//NOTE: ServerConnection property will constuct the object if needed
m_server = new Server(ServerConnection);
}
//NOTE: ServerConnection property will construct the object if needed
m_server = new Server(ServerConnection);
}
else
{
throw new ArgumentException(SR.UnknownServerType(serverType.ToString()));
throw new ArgumentException(SR.UnknownServerType(serverType.ToString()));
}
}
@@ -674,7 +605,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <param name="userName">User name for not trusted connections</param>
/// <param name="password">Password for not trusted connections</param>
/// <param name="xmlParameters">XML string with parameters</param>
public CDataContainer(ServerType serverType, string serverName, bool trusted, string userName, SecureString password, string databaseName, string xmlParameters, string azureAccountToken = null)
public CDataContainer(ServerType serverType, string serverName, bool trusted, string userName, SecureString password, string databaseName, string xmlParameters, string? azureAccountToken = null)
{
this.serverType = serverType;
this.serverName = serverName;
@@ -710,6 +641,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <param name="xmlParameters">XML string with parameters</param>
public CDataContainer(CDataContainer dataContainer, string xmlParameters)
{
//BUGBUG - should we be reusing same SqlConnectionInfoWithConnection if it is available?
System.Diagnostics.Debug.Assert(dataContainer.Server != null, "DataContainer.Server can not be null.");
Server = dataContainer.Server;
this.serverName = dataContainer.serverName;
this.serverType = dataContainer.serverType;
@@ -718,11 +652,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
this.sqlCiWithConnection = dataContainer.connectionInfo as SqlConnectionInfoWithConnection;
if (this.sqlCiWithConnection != null)
{
{
//we want to be notified if it is closed
this.sqlCiWithConnection.ConnectionClosed += new EventHandler(OnSqlConnectionClosed);
}
if (this.connectionInfo is SqlConnectionInfo)
{
System.Diagnostics.Debug.Assert(this.sqlCiWithConnection != null, "CDataContainer.ConnectionInfo setter: for SQL connection info you MUST use SqlConnectionInfoWithConnection derived class!");
}
if (xmlParameters != null)
{
XmlDocument doc = GenerateXmlDocumentFromString(xmlParameters);
@@ -764,20 +703,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
if (site != null)
{
// see if service provider supports INodeInformation interface from the object explorer
// NOTE: we're trying to forcefully set connection information on the data container.
// If this code doesn't execute, then dc.Init call below will result in CDataContainer
// initializing its ConnectionInfo member with a new object contructed off the parameters
// in the XML doc [server name, user name etc]
IManagedConnection managedConnection = site.GetService(typeof(IManagedConnection)) as IManagedConnection;
Trace.TraceInformation("CDataContainer.Init has non-null IServiceProvider");
//see if service provider supports IManagedConnection interface from the object explorer
//NOTE: we're trying to forcefully set connection information on the data container.
//If this code doesn't execute, then dc.Init call below will result in CDataContainer
//initializing its ConnectionInfo member with a new object contructed off the parameters
//in the XML doc [server name, user name etc]
IManagedConnection? managedConnection =
site.GetService(typeof(IManagedConnection)) as IManagedConnection;
if (managedConnection != null)
{
Trace.TraceInformation("CDataContainer.Init has non-null IManagedConnection");
this.SetManagedConnection(managedConnection);
}
}
this.Document = doc;
LoadData();
LoadData();
// finish the initialization
this.Init(doc);
@@ -808,13 +751,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
// NOTE: ServerConnection property will constuct the object if needed
m_server ??= new Server(ServerConnection);
}
else if (this.serverType == ServerType.SQLCE)
{
// do nothing; originally we were only distinguishing between two
// types of servers (OLAP/SQL); as a result for SQLCE we were
// executing the same codepath as for OLAP server which was
// resulting in an exception;
}
}
@@ -837,21 +773,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
if (!bStatus || this.serverName.Length == 0)
{
if (this.sqlCiWithConnection != null)
{
bStatus = param.GetParam("database", ref this.sqlceFilename);
if (bStatus && !string.IsNullOrEmpty(this.sqlceFilename))
{
this.serverType = ServerType.SQLCE;
}
else if (this.sqlCiWithConnection != null)
{
this.serverType = ServerType.SQL;
}
else
{
this.serverType = ServerType.UNKNOWN;
}
}
this.serverType = ServerType.SQL;
}
else
{
this.serverType = ServerType.UNKNOWN;
}
}
else
{
@@ -877,10 +807,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
}
// Ensure there is no password in the XML document
string temp = string.Empty;
string? temp = string.Empty;
if (param.GetParam("password", ref temp))
{
temp = null;
temp = null;
System.Diagnostics.Debug.Assert(false, "Plaintext password found in XML document! This must be fixed!");
throw new SecurityException();
}
@@ -899,7 +831,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// </summary>
/// <param name="managedConnection"></param>
internal void SetManagedConnection(IManagedConnection managedConnection)
{
{
System.Diagnostics.Debug.Assert(this.managedConnection == null, "CDataContainer.SetManagedConnection: overwriting the previous value");
this.managedConnection = managedConnection;
ApplyConnectionInfo(managedConnection.Connection, true);//it will do some extra initialization
@@ -912,7 +845,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <returns>The property value</returns>
public object GetDocumentPropertyValue(string propertyName)
{
object result = null;
object? result = null;
STParameters param = new STParameters(this.Document);
param.GetBaseParam(propertyName, ref result);
@@ -974,6 +907,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// </summary>
private void InitializeObjectNameAndSchema()
{
System.Diagnostics.Debug.Assert(ServerType.SQL == this.serverType, "This method only valid for SQL Servers");
string documentUrn = this.GetDocumentPropertyString("urn");
if (documentUrn.Length != 0)
{
@@ -1003,8 +938,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
string userName,
SecureString password,
string databaseName,
string azureAccountToken)
{
string? azureAccountToken)
{
System.Diagnostics.Debug.Assert(this.serverType == ServerType.SQL, "GetTempSqlConnectionInfoWithConnection should only be called for SQL Server type");
SqlConnectionInfoWithConnection tempCI = new SqlConnectionInfoWithConnection(serverName);
tempCI.SingleConnection = false;
tempCI.Pooled = false;
@@ -1031,7 +968,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnSqlConnectionClosed(object sender, EventArgs e)
private void OnSqlConnectionClosed(object? sender, EventArgs e)
{
}
@@ -1042,7 +979,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// <param name="ci"></param>
private void ApplyConnectionInfo(SqlOlapConnectionInfoBase ci, bool ownConnection)
{
System.Diagnostics.Debug.Assert(this.connectionInfo == null, "CDataContainer.ApplyConnectionInfo: overwriting non-null connection info!");
System.Diagnostics.Debug.Assert(ci != null, "CDataContainer.ApplyConnectionInfo: ci is null!");
this.connectionInfo = ci;
this.ownConnection = ownConnection;
@@ -1050,12 +989,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
this.sqlCiWithConnection = ci as SqlConnectionInfoWithConnection;
if (this.sqlCiWithConnection != null)
{
{
// we want to be notified if it is closed
this.sqlCiWithConnection.ConnectionClosed += new EventHandler(OnSqlConnectionClosed);
}
}
private static bool MustRethrow(Exception exception)
{
bool result = false;
@@ -1132,7 +1071,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// MUST be called, as we'll be closing SQL connection inside this call
/// </summary>
private void Dispose(bool disposing)
{
{
try
{
//take care of live SQL connection
@@ -1175,42 +1114,56 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
this.managedConnection.Close();
}
this.managedConnection = null;
}
}
}
catch (Exception)
{
{
}
}
#endregion
/// <summary>
/// Create a data container object
/// </summary>
/// <param name="connInfo">connection info</param>
/// <param name="databaseExists">flag indicating whether to create taskhelper for existing database or not</param>
internal static CDataContainer CreateDataContainer(
ConnectionInfo connInfo,
ConnectionInfo connInfo,
bool databaseExists = false,
XmlDocument containerDoc = null)
XmlDocument? containerDoc = null)
{
containerDoc ??= CreateDataContainerDocument(connInfo, databaseExists);
var serverConnection = ConnectionService.OpenServerConnection(connInfo, "DataContainer");
var connectionInfoWithConnection = new SqlConnectionInfoWithConnection();
connectionInfoWithConnection.ServerConnection = serverConnection;
return CreateDataContainer(connectionInfoWithConnection, containerDoc);
}
/// <summary>
/// Create a data container object
/// </summary>
/// <param name="connInfo">connection info</param>
/// <param name="databaseExists">flag indicating whether to create taskhelper for existing database or not</param>
internal static CDataContainer CreateDataContainer(
SqlConnectionInfoWithConnection connectionInfoWithConnection,
XmlDocument containerDoc)
{
CDataContainer dataContainer = new CDataContainer(ServerType.SQL, connectionInfoWithConnection, true);
dataContainer.Init(containerDoc);
return dataContainer;
}
internal static System.Security.SecureString BuildSecureStringFromPassword(string password) {
internal static System.Security.SecureString BuildSecureStringFromPassword(string password)
{
var passwordSecureString = new System.Security.SecureString();
if (password != null) {
foreach (char c in password) {
if (password != null)
{
foreach (char c in password)
{
passwordSecureString.AppendChar(c);
}
}

View File

@@ -0,0 +1,900 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#nullable disable
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using Microsoft.Data.SqlClient;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Management
{
public class ActionContext
{
#region private members
/// <summary>
/// Name of the object
/// </summary>
string name;
/// <summary>
/// connection to the server
/// </summary>
private ServerConnection connection;
/// <summary>
/// Connection context
/// </summary>
private string contextUrn;
/// <summary>
/// Parent node in the tree
/// </summary>
//private INodeInformation parent;
/// <summary>
/// Weak reference to the tree node this is paired with
/// </summary>
WeakReference NavigableItemReference;
/// <summary>
/// Property handlers
/// </summary>
//private IList<IPropertyHandler> propertyHandlers;
/// <summary>
/// Property bag
/// </summary>
NameObjectCollection properties;
/// <summary>
/// Object to lock on when we are modifying public state
/// </summary>
private object itemStateLock = new object();
/// <summary>
/// Cached UrnPath
/// </summary>
private string urnPath;
#endregion
#region constructors
public ActionContext(ServerConnection connection, string name, string contextUrn)
{
if (connection == null)
{
throw new ArgumentNullException("connection");
}
if (contextUrn == null)
{
throw new ArgumentNullException("context");
}
if (name == null)
{
throw new ArgumentNullException("name");
}
this.connection = connection;
this.contextUrn = contextUrn;
this.name = name;
properties = new NameObjectCollection();
//propertyHandlers = null;
NavigableItemReference = null;
}
#endregion
#region INodeInformation implementation
public ServerConnection Connection
{
get
{
return this.connection;
}
set
{
lock (this.itemStateLock)
{
this.connection = value;
}
}
}
public string ContextUrn
{
get
{
return this.contextUrn;
}
set
{
lock (this.itemStateLock)
{
this.contextUrn = value;
}
}
}
public string NavigationContext
{
get
{
return GetNavigationContext(this);
}
}
public string UrnPath
{
get
{
this.urnPath ??= ActionContext.BuildUrnPath(this.NavigationContext);
return this.urnPath;
}
}
public string InvariantName
{
get
{
string name = this["UniqueName"] as string;
if (!string.IsNullOrEmpty(name))
return name;
StringBuilder uniqueName = new StringBuilder();
foreach (string urnValue in GetUrnPropertyValues())
{
if (uniqueName.Length > 0)
uniqueName.Append(".");
uniqueName.Append(urnValue);
}
return (uniqueName.Length > 0) ? uniqueName.ToString() : new Urn(ContextUrn).Type;
}
}
/// <summary>
/// property bag for this node
/// </summary>
public object this[string name] => properties[name];
public object CreateObjectInstance()
{
return CreateObjectInstance(this.ContextUrn, this.Connection);
}
#endregion
#region ISfcPropertyProvider implementation
public NameObjectCollection GetPropertySet()
{
return this.properties;
}
#endregion
#region NodeName helper
public string Name
{
get
{
return this.name;
}
set
{
lock (this.itemStateLock)
{
this.name = value;
}
}
}
#endregion
#region property bag support
public NameObjectCollection Properties
{
get
{
return this.properties;
}
set
{
lock (this.itemStateLock)
{
this.properties = value;
}
}
}
#endregion
#region helpers
public static string GetNavigationContext(ActionContext source)
{
string context = source.ContextUrn;
// see if this is a folder
string name = source["UniqueName"] as string;
if (name == null || name.Length == 0)
{
name = source.Name;
}
string queryHint = source["QueryHint"] as string;
if (queryHint == null || queryHint.Length == 0)
{
context = string.Format(
System.Globalization.CultureInfo.InvariantCulture
, "{0}/Folder[@Name='{1}']"
, source.ContextUrn
, Urn.EscapeString(name));
}
else
{
context = string.Format(
System.Globalization.CultureInfo.InvariantCulture
, "{0}/Folder[@Name='{1}' and @Type='{2}']"
, source.ContextUrn
, Urn.EscapeString(name)
, Urn.EscapeString(queryHint));
}
return context;
}
/// <summary>
/// Get the values of the keys in the current objects Urn
/// e.g. For Table[@Name='Foo' and @Schema='Bar'] return Foo and Bar
/// </summary>
/// <returns></returns>
private IEnumerable<string> GetUrnPropertyValues()
{
Urn urn = new Urn(ContextUrn);
Enumerator enumerator = new Enumerator();
RequestObjectInfo request = new RequestObjectInfo(urn, RequestObjectInfo.Flags.UrnProperties);
ObjectInfo info = enumerator.Process(connection, request);
if (info == null || info.UrnProperties == null)
yield break;
// Special order for Schema and Name
if (properties.Contains("Schema"))
yield return urn.GetAttribute("Schema");
if (properties.Contains("Name"))
yield return urn.GetAttribute("Name");
foreach (ObjectProperty obj in info.UrnProperties)
{
if (obj.Name.Equals("Name", StringComparison.OrdinalIgnoreCase) || obj.Name.Equals("Schema", StringComparison.OrdinalIgnoreCase))
continue;
yield return urn.GetAttribute(obj.Name);
}
}
public static string BuildUrnPath(string urn)
{
StringBuilder urnPathBuilder = new StringBuilder(urn != null ? urn.Length : 0);
string folderName = string.Empty;
bool replaceLeafValueInQuery = false;
if (!string.IsNullOrEmpty(urn))
{
Urn urnObject = new Urn(urn);
while (urnObject != null)
{
string objectType = urnObject.Type;
if (string.CompareOrdinal(objectType, "Folder") == 0)
{
folderName = urnObject.GetAttribute("Name").Replace(" ", "");
if (folderName != null)
{
objectType = string.Format("{0}Folder", folderName);
}
}
// Build the path
if (urnPathBuilder.Length > 0)
{
urnPathBuilder.Insert(0, '/');
}
if (objectType.Length > 0)
{
urnPathBuilder.Insert(0, objectType);
}
// Remove one element from the urn
urnObject = urnObject.Parent;
}
// Build the query
if (replaceLeafValueInQuery)
{
// This is another special case for DTS urns.
// When we want to request data for an individual package
// we need to use a special urn with Leaf="2" attribute,
// replacing the Leaf='1' that comes from OE.
urnObject = new Urn(urn.Replace("@Leaf='1'", "@Leaf='2'"));
}
else
{
urnObject = new Urn(urn);
}
}
return urnPathBuilder.ToString();
}
public static object CreateObjectInstance(string urn, ServerConnection serverConnection)
{
if (string.IsNullOrEmpty(urn))
{
return null;
}
try
{
SfcObjectQuery oq = null;
Urn urnObject = new Microsoft.SqlServer.Management.Sdk.Sfc.Urn(urn);
// i have to find domain from Urn.
// DomainInstanceName thrown NotImplemented Exception
// so, i have to walk Urn tree to the top
Urn current = urnObject;
while (current.Parent != null)
{
current = current.Parent;
}
string domainName = current.Type;
if (domainName == "Server")
{
oq = new SfcObjectQuery(new Microsoft.SqlServer.Management.Smo.Server(serverConnection));
}
else
{
SqlConnection connection = serverConnection.SqlConnectionObject;
if (connection == null)
{
return null;
}
// no need to check return value - this method will throw, if domain is incorrect
SfcDomainInfo ddi = Microsoft.SqlServer.Management.Sdk.Sfc.SfcRegistration.Domains[domainName];
ISfcDomain domain = (ISfcDomain)Activator.CreateInstance(ddi.RootType, new SqlStoreConnection(connection));
oq = new SfcObjectQuery(domain);
}
foreach (object obj in oq.ExecuteIterator(new SfcQueryExpression(urn), null, null))
{
return obj;
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError(ex.Message);
return null;
}
return null;
}
#endregion
}
public class DataContainerXmlGenerator
{
#region private members
/// <summary>
/// additional xml to be passed to the dialog
/// </summary>
protected string rawXml = string.Empty;
/// <summary>
/// do not pass this type information to the dialog.
/// e.g. New Database menu item on an existing database should not pass the database name through,
/// so we set itemType as Database.
/// </summary>
protected string? itemType = string.Empty;
/// <summary>
/// Additional query to perform and pass the results to the dialog.
/// </summary>
protected string? invokeMultiChildQueryXPath = null;
private ActionContext context;
/// <summary>
/// The node in the hierarchy that owns this
/// </summary>
public virtual ActionContext Context
{
get { return context; }
set { context = value; }
}
private string mode;
/// <summary>
/// mode
/// </summary>
/// <example>
/// "new" "properties"
/// </example>
public string Mode
{
get { return mode; }
set { mode = value; }
}
#endregion
#region construction
/// <summary>
///
/// </summary>
public DataContainerXmlGenerator(ActionContext context, string mode = "new")
{
this.context = context;
this.mode = mode;
}
#endregion
#region IObjectBuilder implementation
/// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void AddProperty(string name, object value)
{
// RAWXML is xml that is added to the document we're passing to the dialog with no additional
// processing
if (string.Compare(name, "rawxml", StringComparison.OrdinalIgnoreCase) == 0)
{
this.rawXml += value.ToString();
}
// ITEMTYPE is for new menu items where we do not want to pass in the information for this type
// e.g. New Database menu item on an existing database should not pass the database name through,
// so we set ITEMTYPE as Database.
else if (string.Compare(name, "itemtype", StringComparison.OrdinalIgnoreCase) == 0)
{
this.itemType = value.ToString();
}
// Allows us to query below the current level in the enumerator and pass the results through to
// the dialog. Usefull for Do xyz on all for menu's on folders.
else if (string.Compare(name, "multichildqueryxpath", StringComparison.OrdinalIgnoreCase) == 0)
{
this.invokeMultiChildQueryXPath = value.ToString();
}
}
#endregion
#region xml
#region xml document generation
/// <summary>
/// Generate an XmlDocument that contains all of the context needed to launch a dialog
/// </summary>
/// <returns>XmlDocument</returns>
public virtual XmlDocument GenerateXmlDocument()
{
MemoryStream memoryStream = new MemoryStream();
// build the xml
XmlTextWriter xmlWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
// write out the document headers
StartXmlDocument(xmlWriter);
// write xml specific to each connection type
GenerateConnectionXml(xmlWriter);
// generate the xml specific to the item we are being launched against
GenerateItemContext(xmlWriter);
// write out any of out properties to the document
WritePropertiesToXml(xmlWriter);
// close the document headers
EndXmlDocument(xmlWriter);
// make sure everything is commited
xmlWriter.Flush();
// Resets the stream to the beginning
memoryStream.Seek(0, SeekOrigin.Begin);
// done composing the XML string, now build the document
XmlDocument doc = new XmlDocument();
// don't lose leading or trailing whitespace
doc.PreserveWhitespace = true;
// directly create the document from the memoryStream.
// We do this because using an xmlreader in between would an extra
// overhead and it also messes up the new line characters in the original
// stream (converts all \r to \n).-anchals
doc.Load(memoryStream);
return doc;
}
#endregion
#region document start/end
/// <summary>
/// Write the starting elements needed by the dialog framework
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void StartXmlDocument(XmlWriter xmlWriter)
{
XmlGeneratorHelper.StartXmlDocument(xmlWriter);
}
/// <summary>
/// Close the elements needed by the dialog framework
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void EndXmlDocument(XmlWriter xmlWriter)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// close params
xmlWriter.WriteEndElement();
// close formdescription
xmlWriter.WriteEndElement();
}
#endregion
#region server specific generation
/// <summary>
/// Generate the XML that will allow the dialog to connect to the server
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void GenerateConnectionXml(XmlWriter xmlWriter)
{
XmlGeneratorHelper.GenerateConnectionXml(xmlWriter, this.Context);
}
/// <summary>
/// Generate SQL Server specific connection information
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void GenerateSqlConnectionXml(XmlWriter xmlWriter)
{
XmlGeneratorHelper.GenerateSqlConnectionXml(xmlWriter, this.Context);
}
#endregion
#region item context generation
/// <summary>
/// Generate context specific to the node this menu item is being launched against.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void GenerateItemContext(XmlWriter xmlWriter)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// There are two ways we can add context information.
// The first is just off of the node we were launched against. We will break the urn down
// into it's individual components. And pass them to the dialog.
// The second is by performing a query relative to the node we were launched against
// and adding any urns that are returned. No other process will be performed on the urn
// see if we are invoking on single, or multiple items
if (InvokeOnSingleItemOnly())
{
// no query, just an individual item
GenerateIndividualItemContext(xmlWriter);
}
else
{
GenerateMultiItemContext(xmlWriter);
}
}
/// <summary>
/// Generate the context for an individual item.
/// While Generating the context we will break down the Urn to it's individual elements
/// and pass each Type attribute in individually.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void GenerateIndividualItemContext(XmlWriter xmlWriter)
{
XmlGeneratorHelper.GenerateIndividualItemContext(xmlWriter, itemType, this.Context);
}
/// <summary>
/// Generate Context for multiple items.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void GenerateMultiItemContext(XmlWriter xmlWriter)
{
// there will be a query performed
GenerateItemContextFromQuery(xmlWriter);
}
/// <summary>
/// Generate Context with the results of a Query. We will just pass in the multiple
/// Urn's if any that are the results of the query.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void GenerateItemContextFromQuery(XmlWriter xmlWriter)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// generate the request
Request request = new Request();
// only need urn
request.Fields = new string[] { "Urn" };
request.Urn = new Urn(this.Context.ContextUrn + "/" + this.invokeMultiChildQueryXPath);
DataTable dt;
// run the query
Enumerator enumerator = new Enumerator();
EnumResult result = enumerator.Process(this.Context.Connection, request);
if (result.Type == ResultType.DataTable)
{
dt = result;
}
else
{
dt = ((DataSet)result).Tables[0];
}
//TODO: Consider throwing if there are no results.
// Write the results to the XML document
foreach (DataRow row in dt.Rows)
{
WriteUrnInformation(xmlWriter, row[0].ToString());
}
}
/// <summary>
/// Writes a Urn to the XML. If this is an Olap connection we will also write out
/// the Olap Path, which is the AMO equivelent of a Urn.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
/// <param name="urn">Urn to be written</param>
protected virtual void WriteUrnInformation(XmlWriter xmlWriter, string? urn)
{
XmlGeneratorHelper.WriteUrnInformation(xmlWriter, urn, this.Context);
}
/// <summary>
/// Get the list of Urn attributes for this item.
/// </summary>
/// <param name="urn">Urn to be checked</param>
/// <returns>string array of Urn attribute names. This can be zero length but will not be null</returns>
protected virtual string[] GetUrnAttributes(Urn urn)
{
string[]? urnAttributes = null;
if (urn.XPathExpression != null && urn.XPathExpression.Length > 0)
{
int index = urn.XPathExpression.Length - 1;
if (index > 0)
{
System.Collections.SortedList list = urn.XPathExpression[index].FixedProperties;
System.Collections.ICollection keys = list.Keys;
urnAttributes = new string[keys.Count];
int i = 0;
foreach (object o in keys)
{
string? key = o.ToString();
if (key != null)
{
urnAttributes[i++] = key;
}
}
}
}
return urnAttributes != null ? urnAttributes : new string[0];
}
#endregion
#region write properties
/// <summary>
/// Write properties set for this menu item. These can be set to pass different information
/// to the dialog independently of the node type.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
protected virtual void WritePropertiesToXml(XmlWriter xmlWriter)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// mode could indicate properties or new
if (Mode != null && Mode.Length > 0)
{
xmlWriter.WriteElementString("mode", Mode);
}
// raw xml to be passed to the dialog.
// mostly used to control instance awareness.
if (rawXml != null && rawXml.Length > 0)
{
xmlWriter.WriteRaw(rawXml);
}
// mostly used to restrict the context for new item off of an item of that type
// some dialogs require this is passed in so they know what item type they are
// supposed to be creating.
if (this.itemType.Length > 0)
{
xmlWriter.WriteElementString("itemtype", this.itemType);
}
}
#endregion
#endregion
#region protected helpers
/// <summary>
/// Inidicates whether the source is a single or multiple items.
/// </summary>
/// <returns></returns>
protected virtual bool InvokeOnSingleItemOnly()
{
return (this.invokeMultiChildQueryXPath == null || this.invokeMultiChildQueryXPath.Length == 0);
}
#endregion
}
/// <summary>
/// provides helper methods to generate LaunchForm XML and launch certain wizards and dialogs
/// </summary>
public static class XmlGeneratorHelper
{
/// <summary>
/// Write the starting elements needed by the dialog framework
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
public static void StartXmlDocument(XmlWriter xmlWriter)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
xmlWriter.WriteStartElement("formdescription");
xmlWriter.WriteStartElement("params");
}
/// <summary>
/// Writes a Urn to the XML. If this is an Olap connection we will also write out
/// the Olap Path, which is the AMO equivelent of a Urn.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
/// <param name="urn">Urn to be written</param>
public static void WriteUrnInformation(XmlWriter xmlWriter, string urn, ActionContext context)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// write the Urn
xmlWriter.WriteElementString("urn", urn);
}
/// <summary>
/// Generate the XML that will allow the dialog to connect to the server
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
public static void GenerateConnectionXml(XmlWriter xmlWriter, ActionContext context)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// framework also needs to know the type
string serverType = string.Empty;
// Generate Connection specific XML.
if (context.Connection is ServerConnection)
{
GenerateSqlConnectionXml(xmlWriter, context);
serverType = "sql";
}
else
{
System.Diagnostics.Debug.Assert(false, "Warning: Connection type is unknown.");
}
System.Diagnostics.Debug.Assert(serverType.Length > 0, "serverType has not been defined");
xmlWriter.WriteElementString("servertype", serverType);
}
/// <summary>
/// Generate SQL Server specific connection information
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
public static void GenerateSqlConnectionXml(XmlWriter xmlWriter, ActionContext context)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
// write the server name
xmlWriter.WriteElementString("servername", context.Connection.ServerInstance);
}
/// <summary>
/// Generate the context for an individual item.
/// While Generating the context we will break down the Urn to it's individual elements
/// and pass each Type attribute in individually.
/// </summary>
/// <param name="xmlWriter">XmlWriter that these elements will be written to</param>
public static void GenerateIndividualItemContext(XmlWriter xmlWriter, string itemType, ActionContext context)
{
System.Diagnostics.Debug.Assert(xmlWriter != null, "xmlWriter should never be null.");
System.Diagnostics.Debug.Assert(context.ContextUrn != null, "No context available.");
Urn urn = new Urn(context.ContextUrn);
foreach (KeyValuePair<string, string> item in ExtractUrnPart(itemType, urn))
{
xmlWriter.WriteElementString(item.Key, item.Value);
}
// if we are filtering out the information for this level (e.g. new database on a database should not
// pass in the information relating to the selected database. We need to make sure that the Urn we pass
// in is trimmed as well.
Urn sourceUrn = new Urn(context.ContextUrn);
if (itemType != null
&& itemType.Length > 0
&& sourceUrn.Type == itemType)
{
sourceUrn = sourceUrn.Parent;
}
// as well as breaking everything down we will write the Urn directly
// into the XML. Some dialogs will use the individual items, some will
// use the Urn.
WriteUrnInformation(xmlWriter, sourceUrn, context);
}
public static IEnumerable<KeyValuePair<string, string>> ExtractUrnPart(string itemType, Urn urn)
{
// break the urn up into individual xml elements, and add each item
// so Database[@Name='foo']/User[@Name='bar']
// will become
// <database>foo</database>
// <user>bar</user>
// Note: We don't care about server. It is taken care of elsewhere.
// The dialogs need every item to be converted to lower case or they will not
// be able to retrieve the information.
do
{
// server information has already gone in, and is server type specific
// don't get it from the urn
if (urn.Parent != null)
{
// get the attributes for this part of the Urn. For Olap this is ID, for
// everything else it is usually Name, although Schema may also be used for SQL
string[] urnAttributes = UrnUtils.GetUrnAttributes(urn);
// make sure we are not supposed to skip this type. The skip allows us to bring up a "new"
// dialog on an item of that type without passing in context.
// e.g. New Database... on AdventureWorks should not pass in <database>AdventureWorks</Database>
if (string.Compare(urn.Type, itemType, StringComparison.OrdinalIgnoreCase) != 0)
{
for (int i = 0; i < urnAttributes.Length; ++i)
{
// Some Urn attributes require special handling. Don't ask me why
string thisUrnAttribute = urnAttributes[i].ToLower(CultureInfo.InvariantCulture);
string elementName;
switch (thisUrnAttribute)
{
case "schema":
case "categoryid":
elementName = thisUrnAttribute;
break;
default:
elementName = urn.Type.ToLower(CultureInfo.InvariantCulture); // I think it's always the same as thisUrnAttribute but I'm not sure
break;
}
yield return new KeyValuePair<string, string>(elementName, urn.GetAttribute(urnAttributes[i]));
}
}
}
urn = urn.Parent;
}
while (urn != null);
}
}
}

View File

@@ -207,7 +207,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
/// </returns>
protected virtual bool DoPreProcessExecution(RunType runType, out ExecutionMode executionResult)
{
//ask the framework to do normal execution by calling OnRunNOw methods
//ask the framework to do normal execution by calling OnRunNow methods
//of the views one by one
executionResult = ExecutionMode.Success;
return true;

View File

@@ -0,0 +1,135 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.IO;
using System.Text;
using System.Xml;
using Microsoft.SqlServer.Management.Sdk.Sfc;
namespace Microsoft.SqlTools.ServiceLayer.Management
{
/// <summary>
/// Provides helper functions for converting urn enumerator urns
/// to olap path equivilent.
/// </summary>
#if DEBUG || EXPOSE_MANAGED_INTERNALS
public
#else
internal
#endif
class UrnDataPathConverter
{
// static only members
private UrnDataPathConverter()
{
}
/// <summary>
/// Convert a Urn to and olap compatible datapath string
/// </summary>
/// <param name="urn">Source urn</param>
/// <returns>string that Xml that can be used as an olap path</returns>
/// <remarks>
/// Node types are
/// ServerID
/// DatabaseID
/// CubeID
/// DimensionID
/// MeasureGroupID
/// PartitionID
/// MiningStructureID
/// MininingModelID
///
/// These currently map mostly to enuerator types with the addition of ID
///
/// string is of the format <ObjectTypeID>ObjectID</ObjectTypeID><3E>.<ObjectTypeID>ObjectID</ObjectTypeID>
///
/// </remarks>
public static string ConvertUrnToDataPath(Urn urn)
{
String element = String.Empty;
if(urn == null)
{
throw new ArgumentNullException("urn");
}
StringWriter stringWriter = new StringWriter();
XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);
ConvertUrnToDataPath(urn, xmlWriter);
xmlWriter.Flush();
xmlWriter.Close();
return stringWriter.ToString();
}
/// <summary>
/// Datapath conversion helper. Does the conversion using XmlWriter and recursion.
/// </summary>
/// <param name="urn">Urn to be converted</param>
/// <param name="writer">XmlWriter that the results will be written to.</param>
private static void ConvertUrnToDataPath(Urn urn, XmlWriter xmlWriter)
{
if(urn == null)
{
throw new ArgumentNullException("urn");
}
if(xmlWriter == null)
{
throw new ArgumentNullException("xmlWriter");
}
// preserve the order so do the parent first
Urn parent = urn.Parent;
if(parent != null)
{
ConvertUrnToDataPath(parent, xmlWriter);
}
String tag = urn.Type;
// don't put server into the olap path.
if(tag != "OlapServer")
{
xmlWriter.WriteElementString(tag + "ID", urn.GetAttribute("ID"));
}
}
/// <summary>
/// Convert an xml body string that is compatible with a string representation
/// (i.e. deal with < > &)
/// </summary>
/// <param name="s">source</param>
/// <returns>string that can be used as the body for xml stored in a string</returns>
public static string TokenizeXml(string source)
{
System.Diagnostics.Debug.Assert(false, "do not use this function. See bugs 322423 and 115450 in SQLBU Defect tracking");
if(null == source) return String.Empty;
StringBuilder sb = new StringBuilder();
foreach(char c in source)
{
switch(c)
{
case '<':
sb.Append("&lt;");
break;
case '>':
sb.Append("&gt;");
break;
case '&':
sb.Append("&amp;");
break;
default:
sb.Append(c);
break;
}
}
return sb.ToString();
}
}
}

View File

@@ -0,0 +1,46 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using Microsoft.SqlServer.Management.Sdk.Sfc;
namespace Microsoft.SqlTools.ServiceLayer.Management
{
internal class UrnUtils
{
private UrnUtils () { }
/// <summary>
/// Get the list of Urn attributes for this item.
/// </summary>
/// <param name="urn">Urn to be checked</param>
/// <returns>String array of Urn attribute names. This can be zero length but will not be null</returns>
public static string[] GetUrnAttributes(Urn urn)
{
String[]? urnAttributes = null;
if(urn.XPathExpression != null && urn.XPathExpression.Length > 0)
{
int index = urn.XPathExpression.Length - 1;
if(index >= 0)
{
System.Collections.SortedList list = urn.XPathExpression[index].FixedProperties;
System.Collections.ICollection keys = list.Keys;
urnAttributes = new String[keys.Count];
int i = 0;
foreach(object o in keys)
{
string? key = o.ToString();
if (key != null)
urnAttributes[i++] = key;
}
}
}
return urnAttributes != null ? urnAttributes : new String[0];
}
}
}