/* 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 nextLevel); } public class GroupKey : IEquatable { 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 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(); 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 groups = new List(); 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 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 items; public LeafGroup(Search search) : base(search) { this.items = new List(); } 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 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); } } }