mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-29 23:30:10 +01:00
7c0bc50b4a
Inc. Signed-off-by: Gabor Apati-Nagy<gabor.apati-nagy@citrix.com>
533 lines
18 KiB
C#
533 lines
18 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.CustomFields;
|
|
using XenAdmin.Model;
|
|
using XenAPI;
|
|
using System.Xml;
|
|
using XenAdmin.Core;
|
|
using System.Diagnostics;
|
|
|
|
|
|
namespace XenAdmin.XenSearch
|
|
{
|
|
public abstract class Grouping
|
|
{
|
|
protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
public readonly Grouping subgrouping;
|
|
|
|
protected Grouping(Grouping subgrouping)
|
|
{
|
|
this.subgrouping = subgrouping;
|
|
}
|
|
|
|
public abstract String GroupingName { get; }
|
|
|
|
public virtual String GetGroupName(object group)
|
|
{
|
|
return group.ToString();
|
|
}
|
|
|
|
public virtual Icons GetGroupIcon(Object group)
|
|
{
|
|
return Icons.XenCenter;
|
|
}
|
|
|
|
public abstract Object GetGroup(IXenObject o);
|
|
|
|
public virtual bool BelongsAsGroupNotMember(IXenObject o)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public virtual QueryFilter GetRelevantGroupQuery(Search search)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/// <param name="parent">May be null.</param>
|
|
/// <param name="val">May not be null.</param>
|
|
/// <returns>A QueryFilter that returns the objects with the given value (wrt the given parent, if given).
|
|
/// In other words, the QueryFilter that we need when selecting val in the Folder View. May return null if all objects
|
|
/// are to be shown (such as when selecting the Types node).</returns>
|
|
public virtual QueryFilter GetSubquery(object parent, object val)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/// <param name="val">May not be null.</param>
|
|
/// <returns>The Grouping that we need when selecting val in the Folder View, or null if no grouping is required.</returns>
|
|
public virtual Grouping GetSubgrouping(object val)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
#region Marshalling
|
|
|
|
protected virtual void AddXmlAttributes(XmlDocument doc, XmlNode node)
|
|
{
|
|
}
|
|
|
|
public virtual XmlNode ToXmlNode(XmlDocument doc)
|
|
{
|
|
XmlNode node = doc.CreateElement(SearchMarshalling.GetClassName(this));
|
|
|
|
AddXmlAttributes(doc, node);
|
|
|
|
if (subgrouping != null)
|
|
node.AppendChild(subgrouping.ToXmlNode(doc));
|
|
|
|
return node;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Unmarshalling
|
|
|
|
protected Grouping(XmlNode node)
|
|
{
|
|
foreach (XmlNode childNode in node.ChildNodes)
|
|
{
|
|
if (childNode.Name == CustomFieldDefinition.TAG_NAME)
|
|
continue;
|
|
|
|
subgrouping = (Grouping)SearchMarshalling.FromXmlNode(childNode);
|
|
if (subgrouping != null)
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
public class FolderGrouping : Grouping
|
|
{
|
|
public FolderGrouping(Grouping subgrouping)
|
|
: base(subgrouping)
|
|
{
|
|
}
|
|
|
|
public FolderGrouping(XmlNode node)
|
|
: base(node)
|
|
{
|
|
}
|
|
|
|
public override string GroupingName
|
|
{
|
|
get { return Messages.FOLDER; }
|
|
}
|
|
|
|
public override object GetGroup(IXenObject o)
|
|
{
|
|
ComparableList<Folder> folders = Folders.GetAncestorFolders(o);
|
|
if (folders.Count == 0)
|
|
return null;
|
|
else
|
|
{
|
|
List<Folder[]> ans = new List<Folder[]>(1); // See comment in NodeGroup.AddGrouped()
|
|
folders.Reverse();
|
|
ans.Add(folders.ToArray());
|
|
return ans;
|
|
}
|
|
}
|
|
|
|
public override bool BelongsAsGroupNotMember(IXenObject o)
|
|
{
|
|
Folder folder = o as Folder;
|
|
return (folder != null && folder.Parent != null && folder.Parent.IsRootFolder); // i.e., top level folders
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
FolderGrouping other = obj as FolderGrouping;
|
|
return other != null; // All folder groupings are the same!
|
|
}
|
|
|
|
/// <summary>
|
|
/// http://xkcd.com/221/
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override int GetHashCode()
|
|
{
|
|
return 4; // chosen by fair dice roll.
|
|
// guaranteed to be random.
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <typeparam name="T">The expected type of the property to group by</typeparam>
|
|
public class PropertyGrouping<T> : Grouping
|
|
{
|
|
protected readonly Dictionary<T, String> i18ns;
|
|
protected readonly String i18n;
|
|
protected readonly ImageDelegate<T> images;
|
|
protected readonly PropertyAccessor propertyAccessor;
|
|
public readonly PropertyNames property;
|
|
|
|
public PropertyGrouping(PropertyNames property, Grouping subgrouping)
|
|
: base(subgrouping)
|
|
{
|
|
this.property = property;
|
|
this.propertyAccessor = PropertyAccessors.Get(property);
|
|
|
|
this.i18n = PropertyAccessors.PropertyNames_i18n[property];
|
|
this.i18ns = Invert((Dictionary<String, T>)PropertyAccessors.Geti18nFor(property));
|
|
this.images = (ImageDelegate<T>)PropertyAccessors.GetImagesFor(property);
|
|
}
|
|
|
|
public PropertyGrouping(XmlNode node)
|
|
: base(node)
|
|
{
|
|
this.property = (PropertyNames)Enum.Parse(typeof(PropertyNames), Helpers.GetXmlAttribute(node, "property"));
|
|
this.propertyAccessor = PropertyAccessors.Get(property);
|
|
|
|
this.i18n = PropertyAccessors.PropertyNames_i18n[property];
|
|
this.i18ns = Invert((Dictionary<String, T>)PropertyAccessors.Geti18nFor(property));
|
|
this.images = (ImageDelegate<T>)PropertyAccessors.GetImagesFor(property);
|
|
}
|
|
|
|
protected override void AddXmlAttributes(XmlDocument doc, XmlNode node)
|
|
{
|
|
base.AddXmlAttributes(doc, node);
|
|
|
|
SearchMarshalling.AddAttribute(doc, node, "property", property.ToString());
|
|
}
|
|
|
|
private static Dictionary<R, S> Invert<R, S>(Dictionary<S, R> dict)
|
|
{
|
|
if (dict == null)
|
|
return null;
|
|
|
|
Dictionary<R, S> result = new Dictionary<R, S>();
|
|
|
|
foreach (KeyValuePair<S, R> kvp in dict)
|
|
{
|
|
result[kvp.Value] = kvp.Key;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public override Object GetGroup(IXenObject o)
|
|
{
|
|
return propertyAccessor(o);
|
|
}
|
|
|
|
public override QueryFilter GetRelevantGroupQuery(Search search)
|
|
{
|
|
return search.Query.GetSubQueryFor(property);
|
|
}
|
|
|
|
public override QueryFilter GetSubquery(object parent, object val)
|
|
{
|
|
if (typeof(T).IsEnum)
|
|
return new EnumPropertyQuery<T>(property, (T)val, true);
|
|
if (typeof(T) == typeof(string) && property == PropertyNames.tags)
|
|
{
|
|
// !!! Tags come through here. Soon, tags will be proper
|
|
// objects, so this will be replaced.
|
|
return new StringListContainsQuery(property, (string)val, true);
|
|
}
|
|
if (typeof(T) == typeof(string))
|
|
{
|
|
return new ValuePropertyQuery(property, (string)val, true);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public override String GroupingName
|
|
{
|
|
get
|
|
{
|
|
return i18n;
|
|
}
|
|
}
|
|
|
|
public override String GetGroupName(Object group)
|
|
{
|
|
Debug.Assert(i18ns == null || i18ns.ContainsKey((T)group)); //if i18ns is specified, it should contain an item for the group
|
|
|
|
if (!(group is T) || i18ns == null || !i18ns.ContainsKey((T)group))
|
|
return base.GetGroupName(group);
|
|
|
|
return i18ns[(T)group];
|
|
}
|
|
|
|
public override Icons GetGroupIcon(Object group)
|
|
{
|
|
if (images == null || !(group is T))
|
|
return base.GetGroupIcon(group);
|
|
|
|
return images((T)group);
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
PropertyGrouping<T> other = obj as PropertyGrouping<T>;
|
|
if (other == null)
|
|
return false;
|
|
|
|
return property == other.property;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return (int)property;
|
|
}
|
|
}
|
|
|
|
// Special-case Boolean groups in order to make the group name more readable: CA-165366
|
|
public class BoolGrouping : PropertyGrouping<bool>
|
|
{
|
|
protected readonly string i18nfalse;
|
|
|
|
public BoolGrouping(PropertyNames property, Grouping subgrouping)
|
|
: base(property, subgrouping)
|
|
{
|
|
this.i18nfalse = PropertyAccessors.PropertyNames_i18n_false[property];
|
|
}
|
|
|
|
public BoolGrouping(XmlNode node)
|
|
: base(node)
|
|
{
|
|
this.i18nfalse = PropertyAccessors.PropertyNames_i18n_false[property];
|
|
}
|
|
|
|
public string GroupingName2(object group)
|
|
{
|
|
return (!(group is bool) /* shouldn't happen */ || (bool)group) ? i18n : i18nfalse;
|
|
}
|
|
|
|
public override string GetGroupName(object group)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
public class XenModelObjectPropertyGrouping<T>
|
|
: PropertyGrouping<T> where T : XenObject<T>
|
|
{
|
|
public XenModelObjectPropertyGrouping(PropertyNames property, Grouping subgrouping)
|
|
: base(property, subgrouping)
|
|
{
|
|
}
|
|
|
|
public XenModelObjectPropertyGrouping(XmlNode node)
|
|
: base(node)
|
|
{
|
|
}
|
|
|
|
public override bool BelongsAsGroupNotMember(IXenObject o)
|
|
{
|
|
// If the object is not of the group type, it's not a group
|
|
if (!(o is T))
|
|
return false;
|
|
|
|
// Special case: if the type is VM, and the object is not a real VM,
|
|
// it's not a group. This is because snapshots are XMO<VM>s internally,
|
|
// but we want to group them by the real VM they came from.
|
|
VM vm = o as VM;
|
|
if (vm != null && vm.not_a_real_vm)
|
|
return false;
|
|
|
|
// Otherwise this is a group
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public class CustomFieldGrouping : Grouping
|
|
{
|
|
public readonly CustomFieldDefinition definition;
|
|
|
|
public CustomFieldGrouping(CustomFieldDefinition definition, Grouping subgrouping)
|
|
: base(subgrouping)
|
|
{
|
|
this.definition = definition;
|
|
}
|
|
|
|
public CustomFieldGrouping(XmlNode node)
|
|
: base(node)
|
|
{
|
|
foreach (XmlNode childNode in node.ChildNodes)
|
|
{
|
|
if (childNode.Name == CustomFieldDefinition.TAG_NAME)
|
|
{
|
|
definition = new CustomFieldDefinition(childNode);
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw new Exception(string.Format("Custom field declaration with no {0}", CustomFieldDefinition.TAG_NAME));
|
|
}
|
|
|
|
public override string GroupingName
|
|
{
|
|
get { return definition.Name; }
|
|
}
|
|
|
|
public override object GetGroup(IXenObject o)
|
|
{
|
|
return CustomFieldsManager.GetCustomFieldValue(o, definition);
|
|
}
|
|
|
|
public override XmlNode ToXmlNode(XmlDocument doc)
|
|
{
|
|
XmlNode node = base.ToXmlNode(doc);
|
|
|
|
node.AppendChild(definition.ToXmlNode(doc));
|
|
|
|
return node;
|
|
}
|
|
|
|
public override Icons GetGroupIcon(Object group)
|
|
{
|
|
return Icons.CustomField;
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
CustomFieldGrouping other = obj as CustomFieldGrouping;
|
|
if (other == null)
|
|
return false;
|
|
|
|
return definition.Equals(other.definition);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return definition.GetHashCode();
|
|
}
|
|
|
|
public override QueryFilter GetSubquery(object parent, object val)
|
|
{
|
|
System.Diagnostics.Trace.Assert(val != null);
|
|
|
|
CustomFieldDefinition defn =
|
|
CustomFieldsManager.GetCustomFieldDefinition(definition.Name);
|
|
System.Diagnostics.Trace.Assert(defn != null);
|
|
|
|
if (defn.Type == CustomFieldDefinition.Types.String)
|
|
return new CustomFieldQuery(defn, (string)val, StringPropertyQuery.PropertyQueryType.exactmatch);
|
|
else if (defn.Type == CustomFieldDefinition.Types.Date)
|
|
return new CustomFieldDateQuery(defn, (DateTime)val, DatePropertyQuery.PropertyQueryType.exact);
|
|
else return base.GetSubquery(parent, val);
|
|
}
|
|
|
|
public override Grouping GetSubgrouping(object val)
|
|
{
|
|
CustomFieldDefinition defn =
|
|
CustomFieldsManager.GetCustomFieldDefinition(val.ToString());
|
|
return defn == null ? null : new CustomFieldGrouping(defn, null);
|
|
}
|
|
}
|
|
|
|
public class AllCustomFieldsGrouping : Grouping
|
|
{
|
|
public AllCustomFieldsGrouping(Grouping subgrouping)
|
|
: base(subgrouping)
|
|
{
|
|
}
|
|
|
|
public AllCustomFieldsGrouping(XmlNode node)
|
|
: base(node)
|
|
{
|
|
}
|
|
|
|
public override string GroupingName
|
|
{
|
|
get { return Messages.CUSTOM_FIELDS; }
|
|
}
|
|
|
|
public override object GetGroup(IXenObject o)
|
|
{
|
|
return CustomFieldsManager.CustomFieldArrays(o);
|
|
}
|
|
|
|
public override QueryFilter GetSubquery(object parent, object val)
|
|
{
|
|
System.Diagnostics.Trace.Assert(val != null);
|
|
|
|
// Clicked on a key in the tree (which is now in val).
|
|
if (parent == null)
|
|
{
|
|
CustomFieldDefinition defn =
|
|
CustomFieldsManager.GetCustomFieldDefinition(val.ToString());
|
|
System.Diagnostics.Trace.Assert(defn != null);
|
|
|
|
if (defn.Type == CustomFieldDefinition.Types.String)
|
|
return new CustomFieldQuery(defn, String.Empty, StringPropertyQuery.PropertyQueryType.exactmatch);
|
|
else
|
|
return new CustomFieldDateQuery(defn, DateTime.Now, DatePropertyQuery.PropertyQueryType.exact);
|
|
}
|
|
|
|
// Clicked on a value in the tree (key is in parent, value is in val).
|
|
else
|
|
{
|
|
CustomFieldDefinition defn =
|
|
CustomFieldsManager.GetCustomFieldDefinition(parent.ToString());
|
|
System.Diagnostics.Trace.Assert(defn != null);
|
|
|
|
if (defn.Type == CustomFieldDefinition.Types.String)
|
|
return new CustomFieldQuery(defn, val.ToString(), StringPropertyQuery.PropertyQueryType.exactmatch);
|
|
else
|
|
return new CustomFieldDateQuery(defn, (DateTime)val, DatePropertyQuery.PropertyQueryType.exact);
|
|
}
|
|
}
|
|
|
|
public override Grouping GetSubgrouping(object val)
|
|
{
|
|
CustomFieldDefinition defn =
|
|
CustomFieldsManager.GetCustomFieldDefinition(val.ToString());
|
|
return defn == null ? null : new CustomFieldGrouping(defn, null);
|
|
}
|
|
|
|
public override Icons GetGroupIcon(Object group)
|
|
{
|
|
return Icons.CustomField;
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
return (obj is AllCustomFieldsGrouping); // All AllCustomFieldsGrouping's are the same
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return 17;
|
|
}
|
|
}
|
|
}
|