Object Explorer Service (#311)

* moving OE service from an old branch
This commit is contained in:
Leila Lali
2017-04-11 15:50:20 -07:00
committed by GitHub
parent 90861b7d9e
commit d903ba56a9
58 changed files with 15013 additions and 1057 deletions

View File

@@ -0,0 +1,39 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
{
/// <summary>
/// A <see cref="ChildFactory"/> supports creation of <see cref="TreeNode"/> children
/// for a class of objects in the tree. The
/// </summary>
public abstract class ChildFactory
{
/// <summary>
/// The set of applicable parents for which the factory can create children.
/// </summary>
/// <returns>
/// the string names for each <see cref="TreeNode.NodeType"/> that
/// this factory can create children for
/// </returns>
public abstract IEnumerable<string> ApplicableParents();
/// <summary>
/// Expands an element in the
/// </summary>
/// <param name="parent"></param>
/// <returns></returns>
public abstract IEnumerable<TreeNode> Expand(TreeNode parent);
public abstract bool CanCreateChild(TreeNode parent, object context);
public abstract TreeNode CreateChild(TreeNode parent, object context);
// TODO Consider whether Remove operations need to be supported
//public abstract bool CanRemoveChild(TreeNode parent, object context);
//public abstract int GetChildIndexToRemove(TreeNode parent, object context);
}
}

View File

@@ -0,0 +1,177 @@
//
// 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
{
/// <summary>
/// A collection class for <see cref="TreeNode"/>
/// </summary>
public sealed class NodeObservableCollection : ObservableCollection<TreeNode>
{
public event EventHandler Initialized;
private int? numInits;
private static int cleanupBlocker;
public bool IsInitialized
{
get { return numInits.HasValue && numInits == 0; }
}
public bool IsPopulating
{
get { return numInits.HasValue && numInits != 0; }
}
public void BeginInit()
{
if (!numInits.HasValue)
{
numInits = 1;
}
else
{
numInits = numInits + 1;
}
}
public void EndInit()
{
IList<TreeNode> empty = null;
EndInit(null, ref empty);
}
public void EndInit(TreeNode parent, ref IList<TreeNode> deferredChildren)
{
if (numInits.HasValue &&
numInits.Value == 1)
{
try
{
DoSort();
if (deferredChildren != null)
{
// Set the parents so the children know how to sort themselves
foreach (var item in deferredChildren)
{
item.Parent = parent;
}
deferredChildren = deferredChildren.OrderBy(x => x).ToList();
// Add the deferredChildren
foreach (var item in deferredChildren)
{
this.Add(item);
}
}
}
finally
{
if (deferredChildren != null)
{
deferredChildren.Clear();
}
numInits = numInits - 1;
}
Initialized?.Invoke(this, EventArgs.Empty);
}
else
{
numInits = numInits - 1;
}
}
/// <summary>
/// Repositions this child in the list
/// </summary>
public void ReSortChild(TreeNode child)
{
if (child == null)
return;
List<TreeNode> sorted = this.OrderBy(x => x).ToList();
// Remove without cleanup
try
{
cleanupBlocker++;
Remove(child);
}
finally
{
cleanupBlocker--;
}
// Then insert
for (int i = 0; i < sorted.Count; i++)
{
if (sorted[i] == child)
{
Insert(i, child);
return;
}
}
}
protected override void RemoveItem(int index)
{
// Cleanup all the children
Cleanup(this[index]);
base.RemoveItem(index);
}
protected override void ClearItems()
{
// Cleanup all the children
foreach (var child in this)
{
Cleanup(child);
}
base.ClearItems();
}
private static void Cleanup(TreeNode parent)
{
if (cleanupBlocker > 0 ||
parent.Parent == null)
return;
// TODO implement cleanup policy / pattern
//ICleanupPattern parentAsCleanup = parent as ICleanupPattern;
//if (parentAsCleanup != null)
// parentAsCleanup.DoCleanup();
//foreach (var child in parent.Children)
//{
// Cleanup(child);
//}
parent.Parent = null;
}
private void DoSort()
{
List<TreeNode> sorted = this.OrderBy(x => x).ToList();
for (int i = 0; i < sorted.Count(); i++)
{
int index = IndexOf(sorted[i]);
if (index != i)
{
Move(IndexOf(sorted[i]), i);
}
}
}
}
}

View File

@@ -0,0 +1,141 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
{
/// <summary>
/// Enum listing possible node types in the object explorer tree
/// </summary>
// TODO Consider replacing this with an auto-gen'd version
public enum NodeTypes
{
None,
SqlServersRoot,
DatabaseInstance,
DacInstance,
ServerInstance,
ScalarValuedFunctionInstance,
TableValuedFunctionInstance,
AggregateFunctionInstance,
FileGroupInstance,
StoredProcedureInstance,
UserDefinedTableTypeInstance,
ViewInstance,
TableInstance,
HistoryTableInstance,
Databases,
ExternalResources,
ServerLevelSecurity,
ServerLevelServerObjects,
ServerLevelManagement,
SystemDatabases,
ServerLevelLinkedServerLogins,
ServerLevelServerAudits,
ServerLevelCryptographicProviders,
ServerLevelCredentials,
ServerLevelServerRoles,
ServerLevelLogins,
ServerLevelEventSessions,
ServerLevelServerAuditSpecifications,
ServerLevelEventNotifications,
ServerLevelErrorMessages,
ServerLevelServerTriggers,
ServerLevelLinkedServers,
ServerLevelEndpoints,
DacInstancesFolder,
Tables,
Views,
Synonyms,
Programmability,
ServiceBroker,
Storage,
Security,
SystemTables,
FileTables,
SystemViews,
StoredProcedures,
Functions,
ExtendedStoredProcedures,
DatabaseTriggers,
Defaults,
Rules,
Types,
Assemblies,
MessageTypes,
Contracts,
Queues,
Services,
Routes,
DatabaseAndQueueEventNotifications,
RemoteServiceBindings,
BrokerPriorities,
FileGroups,
FullTextCatalogs,
FullTextStopLists,
SqlLogFiles,
PartitionFunctions,
PartitionSchemes,
SearchPropertyLists,
Users,
Roles,
Schemas,
AsymmetricKeys,
Certificates,
SymmetricKeys,
DatabaseEncryptionKeys,
MasterKeys,
Signatures,
DatabaseAuditSpecifications,
Columns,
Keys,
Constraints,
Triggers,
Indexes,
Statistics,
TableValuedFunctions,
ScalarValuedFunctions,
AggregateFunctions,
SystemDataTypes,
UserDefinedDataTypes,
UserDefinedTableTypes,
UserDefinedTypes,
XmlSchemaCollections,
SystemExactNumerics,
SystemApproximateNumerics,
SystemDateAndTimes,
SystemCharacterStrings,
SystemUnicodeCharacterStrings,
SystemBinaryStrings,
SystemOtherDataTypes,
SystemClrDataTypes,
SystemSpatialDataTypes,
UserDefinedTableTypeColumns,
UserDefinedTableTypeKeys,
UserDefinedTableTypeConstraints,
SystemStoredProcedures,
StoredProcedureParameters,
TableValuedFunctionParameters,
ScalarValuedFunctionParameters,
AggregateFunctionParameters,
DatabaseRoles,
ApplicationRoles,
FileGroupFiles,
SystemMessageTypes,
SystemContracts,
SystemServices,
SystemQueues,
Sequences,
SecurityPolicies,
DatabaseScopedCredentials,
ExternalTables,
ExternalResourceInstance,
ExternalDataSources,
ExternalFileFormats,
ExternalTableInstance,
AlwaysEncryptedKeys,
ColumnMasterKeys,
ColumnEncryptionKeys
}
}

View File

@@ -0,0 +1,329 @@
//
// 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
{
/// <summary>
/// Base class for elements in the object explorer tree. Provides common methods for tree navigation
/// and other core functionality
/// </summary>
public class TreeNode : IComparable<TreeNode>
{
private NodeObservableCollection children = new NodeObservableCollection();
private TreeNode parent;
private string nodePath;
private string label;
private ObjectExplorerService objectExplorerService;
public const char PathPartSeperator = '/';
/// <summary>
/// Constructor with no required inputs
/// </summary>
public TreeNode()
{
}
/// <summary>
/// Constructor that accepts a label to identify the node
/// </summary>
/// <param name="value">Label identifying the node</param>
public TreeNode(string value)
{
// We intentionally do not valid this being null or empty since
// some nodes may need to set it
NodeValue = value;
}
/// <summary>
/// Value describing this node
/// </summary>
public string NodeValue { get; set; }
/// <summary>
/// The type of the node - for example Server, Database, Folder, Table
/// </summary>
public string NodeType { get; set; }
/// <summary>
/// Enum defining the type of the node - for example Server, Database, Folder, Table
/// </summary>
public NodeTypes NodeTypeId { get; set; }
/// <summary>
/// Label to display to the user, describing this node.
/// If not explicitly set this will fall back to the <see cref="NodeValue"/> but
/// for many nodes such as the server, the display label will be different
/// to the value.
/// </summary>
public string Label {
get
{
if(label == null)
{
return NodeValue;
}
return label;
}
set
{
label = value;
}
}
/// <summary>
/// Is this a leaf node (in which case no children can be generated) or
/// is it expandable?
/// </summary>
public bool IsAlwaysLeaf { get; set; }
/// <summary>
/// Message to show if this Node is in an error state. This indicates
/// that children could be retrieved
/// </summary>
public string ErrorStateMessage { get; set; }
/// <summary>
/// Parent of this node
/// </summary>
public TreeNode Parent
{
get
{
return parent;
}
set
{
parent = value;
// Reset the node path since it's no longer valid
nodePath = null;
}
}
/// <summary>
/// Path identifying this node: for example a table will be at ["server", "database", "tables", "tableName"].
/// This enables rapid navigation of the tree without the need for a global registry of elements.
/// The path functions as a unique ID and is used to disambiguate the node when sending requests for expansion.
/// A common ID is needed since processes do not share address space and need a unique identifier
/// </summary>
public string GetNodePath()
{
if (nodePath == null)
{
GenerateNodePath();
}
return nodePath;
}
private void GenerateNodePath()
{
string path = "";
ObjectExplorerUtils.VisitChildAndParents(this, node =>
{
if (string.IsNullOrEmpty(node.NodeValue))
{
// Hit a node with no NodeValue. This indicates we need to stop traversing
return false;
}
// Otherwise add this value to the beginning of the path and keep iterating up
path = string.Format(CultureInfo.InvariantCulture,
"{0}{1}{2}", node.NodeValue, string.IsNullOrEmpty(path) ? "" : PathPartSeperator.ToString(), path);
return true;
});
nodePath = path;
}
public TreeNode FindNodeByPath(string path)
{
TreeNode nodeForPath = ObjectExplorerUtils.FindNode(this, node =>
{
return node.GetNodePath() == path;
}, nodeToFilter =>
{
return path.StartsWith(nodeToFilter.GetNodePath());
});
return nodeForPath;
}
/// <summary>
/// Converts to a <see cref="NodeInfo"/> object for serialization with just the relevant properties
/// needed to identify the node
/// </summary>
/// <returns></returns>
public NodeInfo ToNodeInfo()
{
return new NodeInfo()
{
IsLeaf = this.IsAlwaysLeaf,
Label = this.Label,
NodePath = this.GetNodePath(),
NodeType = this.NodeType
};
}
/// <summary>
/// Expands this node and returns its children
/// </summary>
/// <returns>Children as an IList. This is the raw children collection, not a copy</returns>
public IList<TreeNode> Expand()
{
// TODO consider why solution explorer has separate Children and Items options
if (children.IsInitialized)
{
return children;
}
PopulateChildren();
return children;
}
/// <summary>
/// Gets a readonly view of the currently defined children for this node.
/// This does not expand the node at all
/// Since the tree needs to keep track of parent relationships, directly
/// adding to the list is not supported.
/// </summary>
/// <returns><see cref="IList{TreeNode}"/> containing all children for this node</returns>
public IList<TreeNode> GetChildren()
{
return new ReadOnlyCollection<TreeNode>(children);
}
/// <summary>
/// Adds a child to the list of children under this node
/// </summary>
/// <param name="newChild"><see cref="TreeNode"/></param>
public void AddChild(TreeNode newChild)
{
Validate.IsNotNull(nameof(newChild), newChild);
children.Add(newChild);
newChild.Parent = this;
}
/// <summary>
/// Optional context to help with lookup of children
/// </summary>
public virtual object GetContext()
{
return null;
}
/// <summary>
/// Helper method to convert context to expected format
/// </summary>
/// <typeparam name="T">Type to convert to</typeparam>
/// <returns>context as expected type of null if it doesn't match</returns>
public T GetContextAs<T>()
where T : class
{
return GetContext() as T;
}
public T ParentAs<T>()
where T : TreeNode
{
return Parent as T;
}
protected void PopulateChildren()
{
Debug.Assert(IsAlwaysLeaf == false);
SmoQueryContext context = this.GetContextAs<SmoQueryContext>();
if (children.IsPopulating || context == null)
return;
children.Clear();
BeginChildrenInit();
try
{
IEnumerable<ChildFactory> childFactories = context.GetObjectExplorerService().GetApplicableChildFactories(this);
if (childFactories != null)
{
foreach (var factory in childFactories)
{
IEnumerable<TreeNode> items = factory.Expand(this);
if (items != null)
{
foreach (TreeNode item in items)
{
children.Add(item);
item.Parent = this;
}
}
}
}
}
finally
{
EndChildrenInit();
}
}
public void BeginChildrenInit()
{
children.BeginInit();
}
public void EndChildrenInit()
{
children.EndInit();
// TODO consider use of deferred children and if it's necessary
// children.EndInit(this, ref deferredChildren);
}
/// <summary>
/// Sort Priority to help when ordering elements in the tree
/// </summary>
public int? SortPriority { get; set; }
protected virtual int CompareSamePriorities(TreeNode thisItem, TreeNode otherItem)
{
return string.Compare(thisItem.NodeValue, otherItem.NodeValue, StringComparison.OrdinalIgnoreCase);
}
public int CompareTo(TreeNode other)
{
if (!this.SortPriority.HasValue &&
!other.SortPriority.HasValue)
{
return CompareSamePriorities(this, other);
}
if (this.SortPriority.HasValue &&
!other.SortPriority.HasValue)
{
return -1; // this is above other
}
if (!this.SortPriority.HasValue)
{
return 1; // this is below other
}
// Both have sort priority
int priDiff = this.SortPriority.Value - other.SortPriority.Value;
if (priDiff < 0)
return -1; // this is below other
if (priDiff == 0)
return 0;
return 1;
}
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
{
public class TreeNodeWithContext
{
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
{
/// <summary>
/// Indicates which type of server a given node type is valid for
/// </summary>
[Flags]
public enum ValidForFlag
{
Sql2005 = 0x01,
Sql2008 = 0x02,
Sql2012 = 0x04,
Sql2014 = 0x08,
Azure = 0x10,
NotDebugInstance = 0x20,
NotContainedUser = 0x40,
AzureV12 = 0x80,
Sql2016 = 0x100,
CanConnectToMaster = 0x200,
CanViewSecurity = 0x400,
SqlvNext = 0x800,
All = Sql2005 | Sql2008 | Sql2012 | Sql2014 | Sql2016 | SqlvNext | Azure | AzureV12
}
}