xenadmin/XenModel/XenSearch/GroupAlg.cs
Konstantina Chremmou ae22560ce8 Converted all extension get properties of the API classes to methods in order to
prevent them from being serialised alongside the API properties. This will also
be useful for moving the API bindings out of XenModel.

Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
2017-09-03 03:35:30 +01:00

571 lines
19 KiB
C#

/* Copyright (c) Citrix Systems, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms,
* with or without modification, are permitted provided
* that the following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using XenAdmin.Model;
using XenAdmin.Network;
using System.Collections;
using XenAPI;
using XenAdmin.Core;
// I think this is more complicated then it needs to be. Rather than have three different types of nodes,
// depending on the grouping of the next level, we should just have one type of node and do something like
// foreach (IXenObject o in XenSearchableObjects)
// foreach (Grouping g in search.Grouping)
// add node for the group;
// add node for the object;
// SRET 2009-04-28
namespace XenAdmin.XenSearch
{
public interface IAcceptGroups
{
IAcceptGroups Add(Grouping grouping, Object group, int indent);
void FinishedInThisGroup(bool defaultExpand);
}
public abstract class Group
{
public static Group GetGrouped(Search search)
{
Group group = GetGroupFor(null, search, search.EffectiveGrouping);
GetGrouped(search, group);
return group;
}
protected static Group GetGroupFor(Grouping grouping, Search search, Grouping subgrouping)
{
if (grouping is FolderGrouping)
return new FolderGroup(search, grouping);
else if (subgrouping == null)
return new LeafGroup(search);
else
return new NodeGroup(search, subgrouping);
}
private static void GetGrouped(Search search, Group group)
{
search.Items = 0;
foreach (IXenConnection connection in ConnectionsManager.XenConnectionsCopy)
{
if (connection.IsConnected && Helpers.GetPoolOfOne(connection) != null)
{
foreach (IXenObject o in connection.Cache.XenSearchableObjects)
if (!Hide(o))
group.FilterAdd(search.Query,o );
}
else
{
// Fake out disconnected connections with a host.
// We do this because IXenConnection is not an IXenObject, and so
// is not XenSearchable and cannot be put in the treeview.
Host disconnectedHost = new Host();
disconnectedHost.opaque_ref = connection.HostnameWithPort;
disconnectedHost.name_label = Helpers.GetName(connection);
disconnectedHost.Connection = connection;
group.FilterAdd(search.Query, disconnectedHost);
}
}
}
private static bool Hide(IXenObject o)
{
if (XenAdminConfigManager.Provider.ObjectIsHidden(o.opaque_ref))
return true;
if (o is VM)
{
VM vm = o as VM;
if (vm.is_control_domain
|| !vm.Show(XenAdminConfigManager.Provider.ShowHiddenVMs))
return true;
// Hide VMs on non-live hosts
Host host = vm.Home();
if (host != null && !host.IsLive())
return true;
}
else if (o is SR)
{
SR sr = o as SR;
if (!sr.Show(XenAdminConfigManager.Provider.ShowHiddenVMs) || sr.IsToolsSR())
return true;
// Hide SRs on non-live hosts
Host host = sr.Home();
if (host != null && !host.IsLive())
return true;
}
else if (o is XenAPI.Network)
{
XenAPI.Network network = o as XenAPI.Network;
return !network.Show(XenAdminConfigManager.Provider.ShowHiddenVMs);
}
else if (o is Folder)
{
// Hide the root folder
Folder folder = o as Folder;
return folder.IsRootFolder;
}
return false;
}
protected readonly Search search;
protected Group(Search search)
{
this.search = search;
}
protected int Compare(Object _1, Object _2)
{
return Compare(_1, _2, search);
}
protected int CompareGroupKeys(GroupKey _1, GroupKey _2)
{
return Compare(_1.key, _2.key);
}
public static int Compare(Object _1, Object _2, Search search)
{
// 1) Non-IXMOs always come before IXMOs. This is because if they're at the
// same level, they represent grouped items vs ungrouped items.
IXenObject i1 = _1 as IXenObject;
IXenObject i2 = _2 as IXenObject;
if (i1 != null && i2 == null)
return 1;
if (i1 == null && i2 != null)
return -1;
// 2) Try and separate them using the requested sorting, if any
if (search != null && search.Sorting != null)
{
foreach (Sort sort in search.Sorting)
{
int r = sort.Compare(_1, _2);
if (r != 0)
return r;
}
}
// 3) If that failed, sort by type (for IXMOs);
// if they're of the same type, the built-in sort for the type (not always alphabetical: see CA-27829);
// if those still don't separate them, object name.
if (i1 != null && i2 != null)
{
int r = CompareByType(i1, i2);
if (r != 0)
return r;
}
if (_1.GetType() == _2.GetType() && _1 is IComparable)
{
int r = Comparer.Default.Compare(_1, _2);
if (r != 0)
return r;
}
string s1 = (i1 == null ? _1.ToString() : Helpers.GetName(i1));
string s2 = (i2 == null ? _2.ToString() : Helpers.GetName(i2));
return StringUtility.NaturalCompare(s1, s2);
}
// Compare two IXMOs by type. I wanted to use the proper user-facing type from
// PropertyAccessors.properties[PropertyNames.type] but it turned out to be much
// too slow. Instead we use the type of the object with a few tweaks to sort
// important objects first.
private static int CompareByType(IXenObject _1, IXenObject _2)
{
string t1 = TypeOf(_1);
string t2 = TypeOf(_2);
return t1.CompareTo(t2);
}
private static string TypeOf(IXenObject o)
{
if (o is Folder)
return "10";
if (o is Pool)
return "20";
if (o is Host)
return "30";
VM vm = o as VM;
if (vm != null && vm.is_a_real_vm())
return "40";
return o.GetType().ToString();
}
private void FilterAdd(Query query, IXenObject o)
{
if (query == null || !query.Match(o))
return;
Add(o);
}
public abstract void Add(IXenObject o);
public virtual bool Populate(IAcceptGroups adapter)
{
return Populate(adapter, 0, true);
}
public abstract bool Populate(IAcceptGroups adapter, int indent, bool defaultExpand);
public abstract void PopulateFor(IAcceptGroups adapter, GroupKey group, int indent, bool defaultExpand);
public abstract void GetNextLevel(List<GroupKey> nextLevel);
}
public class GroupKey : IEquatable<GroupKey>
{
public Grouping grouping;
public object key;
public GroupKey(Grouping grouping, object key)
{
this.grouping = grouping;
this.key = key;
}
public override int GetHashCode()
{
return key.GetHashCode();
}
public bool Equals(GroupKey other)
{
return other != null && grouping.Equals(other.grouping) && key.Equals(other.key);
}
public override bool Equals(object obj)
{
GroupKey other = obj as GroupKey;
return other != null && Equals(other);
}
}
public abstract class AbstractNodeGroup : Group
{
protected readonly Dictionary<GroupKey, Group> grouped;
protected Group ungrouped = null; //this is late bound
protected readonly Grouping grouping;
protected AbstractNodeGroup(Search search, Grouping grouping)
: base(search)
{
this.grouped = new Dictionary<GroupKey, Group>();
this.grouping = grouping;
}
public override bool Populate(IAcceptGroups adapter)
{
return Populate(adapter, 0, false);
}
public override bool Populate(IAcceptGroups adapter, int indent, bool defaultExpand)
{
bool added = false;
List<GroupKey> groups = new List<GroupKey>();
GetNextLevel(groups);
groups.Sort(CompareGroupKeys);
foreach (GroupKey group in groups)
{
IAcceptGroups subAdapter = adapter.Add(group.grouping, group.key, indent);
if (subAdapter == null)
continue;
added = true;
PopulateFor(subAdapter, group, indent + 1, defaultExpand);
}
adapter.FinishedInThisGroup(defaultExpand);
return added;
}
public override void PopulateFor(IAcceptGroups adapter, GroupKey group, int indent, bool defaultExpand)
{
if (grouped.ContainsKey(group))
{
grouped[group].Populate(adapter, indent, defaultExpand);
}
else if (ungrouped != null)
{
ungrouped.PopulateFor(adapter, group, indent, defaultExpand);
}
}
public override void GetNextLevel(List<GroupKey> nextLevel)
{
nextLevel.AddRange(grouped.Keys);
if(ungrouped != null)
ungrouped.GetNextLevel(nextLevel);
}
public Group FindOrAddSubgroup(Grouping grouping, object o, Grouping subgrouping)
{
GroupKey key = new GroupKey(grouping, o);
if (!grouped.ContainsKey(key))
grouped[key] = GetGroupFor(grouping, search, subgrouping);
return grouped[key];
}
}
public class NodeGroup : AbstractNodeGroup
{
public NodeGroup(Search search, Grouping grouping)
: base(search, grouping)
{
}
public override void Add(IXenObject o)
{
if (grouping.BelongsAsGroupNotMember(o))
{
GroupKey key = new GroupKey(grouping, o);
if (!grouped.ContainsKey(key))
grouped[key] = GetGroupFor(grouping, search, grouping.subgrouping);
return;
}
Object group = grouping.GetGroup(o);
if (group == null)
{
AddUngrouped(o);
return;
}
IList groups = group as IList;
if (groups == null)
{
AddGrouped(o, group);
return;
}
if (groups.Count == 0)
{
AddUngrouped(o);
return;
}
foreach (Object g in groups)
{
if (g == null)
{
AddUngrouped(o);
continue;
}
AddGrouped(o, g);
}
}
private void AddGrouped(IXenObject o, Object group)
{
// We sometimes need to apply the query to the group. For example, suppose VM 1
// is connected to Network 1 and Network 2. Then "VMs grouped by Network" will
// show
//
// -- Network 1
// -- VM 1
// -- Network 2
// -- VM 1
//
// So far so good. Now consider "VMs connected to Network 1 grouped by Network".
// Without the following piece of code, that would show exactly the same thing:
// because the second VM 1 is connected to Network 1, and is correctly grouped
// in Network 2. But what the user presumably wanted to see was just Network 1
// with its VMs under it: and that is achieved by applying the filter to the
// networks.
//
// The consequence when adding new searches is that a search that returns a
// XenObject of type T must return (T)o rather than null when o is a T,
// otherwise if you both group and filter by that type, the group will fail
// to match the filter and you'll get no results.
//
IXenObject groupModelObject = group as IXenObject;
if (groupModelObject != null && XenAdminConfigManager.Provider.ObjectIsHidden(groupModelObject.opaque_ref))
{
return;
}
if (search.Query != null && groupModelObject != null)
{
QueryFilter subquery = grouping.GetRelevantGroupQuery(search);
if (subquery != null && subquery.Match(groupModelObject) == false)
return;
}
Group nextGroup;
// Some types of grouping can add several levels to the hierarchy.
// This should not be confused with the IList in Add(IXenObject o):
// that adds the item to several groups, whereas this adds it to a
// single group several levels deep. In order to reach here,
// grouping.GetGroup(o) must return a list of arrays.
//
// NB We don't do the GetRelevantGroupQuery() check as above for the
// groups added in this way because we never need it: but if we ever
// add it, we should probably do a first pass to check all the groups
// first before adding any.
Array groups = group as Array;
if (groups != null)
{
nextGroup = this;
for (int i = 0; i < groups.Length; ++i)
{
Grouping gr = (i == groups.Length - 1 ? grouping.subgrouping : grouping);
nextGroup = (nextGroup as AbstractNodeGroup).FindOrAddSubgroup(grouping, groups.GetValue(i), gr);
}
}
else
{
nextGroup = FindOrAddSubgroup(grouping, group, grouping.subgrouping);
}
nextGroup.Add(o);
}
protected void AddUngrouped(IXenObject o)
{
if (!XenAdminConfigManager.Provider.ObjectIsHidden(o.opaque_ref))
{
if (ungrouped == null)
ungrouped = GetGroupFor(grouping, search, grouping.subgrouping);
ungrouped.Add(o);
}
}
}
public class FolderGroup : AbstractNodeGroup
{
public FolderGroup(Search search, Grouping grouping)
: base(search, grouping)
{
}
public override void Add(IXenObject o)
{
if (o is Folder)
{
GroupKey key = new GroupKey(grouping, o);
if (!grouped.ContainsKey(key))
grouped[key] = new FolderGroup(search, grouping);
}
else
{
if (ungrouped == null)
ungrouped = new LeafGroup(search);
ungrouped.Add(o);
}
}
}
public class LeafGroup : Group
{
internal readonly List<IXenObject> items;
public LeafGroup(Search search)
: base(search)
{
this.items = new List<IXenObject>();
}
public override void Add(IXenObject o)
{
if (o is Folder && items.Contains(o)) // one folder can appear on several connections
return;
search.Items++;
items.Add(o);
}
public override bool Populate(IAcceptGroups adapter, int indent, bool defaultExpand)
{
bool added = false;
items.Sort(Compare);
foreach (IXenObject o in items)
{
IAcceptGroups subAdapter = adapter.Add(null, o, indent);
if (subAdapter != null)
{
added = true;
subAdapter.FinishedInThisGroup(defaultExpand);
}
}
adapter.FinishedInThisGroup(defaultExpand);
return added;
}
public override void GetNextLevel(List<GroupKey> nextLevel)
{
foreach(IXenObject item in items)
nextLevel.Add(new GroupKey(null, item));
}
public override void PopulateFor(IAcceptGroups adapter, GroupKey group, int indent, bool defaultExpand)
{
adapter.FinishedInThisGroup(defaultExpand);
}
}
}