/* Copyright (c) Cloud Software Group, Inc. * * 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 System.ComponentModel; using System.Windows.Forms; using XenAdmin.CustomFields; using XenAdmin.XenSearch; using XenAPI; using XenAdmin.Core; using XenAdmin.Network; using XenAdmin.Model; using XenCenterLib; namespace XenAdmin.Controls.XenSearch { public partial class QueryElement : UserControl { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private static readonly List queryTypes = new List(); private static readonly List customFields = new List(); private static readonly QueryType DefaultQueryType; static QueryElement() { DefaultQueryType = new DummyQueryType(0, ObjectTypes.None); queryTypes.Add(DefaultQueryType); queryTypes.Add(new GroupQueryType(0, ObjectTypes.AllIncFolders, GroupQuery.GroupQueryType.And)); queryTypes.Add(new GroupQueryType(0, ObjectTypes.AllIncFolders, GroupQuery.GroupQueryType.Or)); queryTypes.Add(new GroupQueryType(0, ObjectTypes.AllIncFolders, GroupQuery.GroupQueryType.Nor)); queryTypes.Add(new EnumPropertyQueryType(1, ObjectTypes.None, PropertyNames.type)); queryTypes.Add(new UuidQueryType(1, ObjectTypes.AllIncFolders & ~ObjectTypes.VDI | ObjectTypes.Appliance)); // but only actually shown for parent/child queries (see WantQueryType()). Too slow for VDIs and not very useful. queryTypes.Add(new StringPropertyQueryType(1, ObjectTypes.AllIncFolders | ObjectTypes.Appliance, PropertyNames.label)); queryTypes.Add(new StringPropertyQueryType(1, ObjectTypes.AllExcFolders | ObjectTypes.Appliance, PropertyNames.description)); queryTypes.Add(new UuidStringQueryType(1, ObjectTypes.AllExcFolders)); queryTypes.Add(new TagQueryType(1, ObjectTypes.AllExcFolders)); // Replaced by new ParentChildQueryTypes below queryTypes.Add(new XenModelObjectPropertyQueryType(2, ObjectTypes.None, PropertyNames.pool)); queryTypes.Add(new HostQueryType(2, ObjectTypes.None)); queryTypes.Add(new LongQueryType(3, ObjectTypes.VM, Messages.MEMORY, PropertyNames.memory, Util.BINARY_MEGA, Messages.VAL_MEGB)); queryTypes.Add(new IPAddressQueryType(3, ObjectTypes.VM | ObjectTypes.Server | ObjectTypes.LocalSR | ObjectTypes.RemoteSR, PropertyNames.ip_address)); queryTypes.Add(new DatePropertyQueryType(3, ObjectTypes.VM, PropertyNames.start_time)); queryTypes.Add(new EnumPropertyQueryType(3, ObjectTypes.VM, PropertyNames.power_state)); queryTypes.Add(new EnumPropertyQueryType(3, ObjectTypes.VM, PropertyNames.virtualisation_status)); queryTypes.Add(new ValuePropertyQueryType(3, ObjectTypes.VM, PropertyNames.os_name)); queryTypes.Add(new EnumPropertyQueryType(3, ObjectTypes.VM, PropertyNames.ha_restart_priority)); queryTypes.Add(new BooleanQueryType(3, ObjectTypes.VM, PropertyNames.read_caching_enabled)); queryTypes.Add(new BooleanQueryType(3, ObjectTypes.VM, PropertyNames.vendor_device_state)); queryTypes.Add(new EnumPropertyQueryType(3, /*ObjectTypes.LocalSR | ObjectTypes.RemoteSR*/ ObjectTypes.None, PropertyNames.sr_type)); queryTypes.Add(new LongQueryType(4, ObjectTypes.VDI, Messages.SIZE, PropertyNames.size, Util.BINARY_GIGA, Messages.VAL_GIGB)); queryTypes.Add(new BooleanQueryType(4, ObjectTypes.LocalSR | ObjectTypes.RemoteSR | ObjectTypes.VDI, PropertyNames.shared)); queryTypes.Add(new BooleanQueryType(4, ObjectTypes.Pool, PropertyNames.ha_enabled)); queryTypes.Add(new BooleanQueryType(4, ObjectTypes.Pool, PropertyNames.isNotFullyUpgraded)); queryTypes.Add(new NullQueryType(4, ObjectTypes.Server | ObjectTypes.VM, PropertyNames.pool, false, Messages.IS_IN_A_POOL)); queryTypes.Add(new NullQueryType(4, ObjectTypes.Server | ObjectTypes.VM, PropertyNames.pool, true, Messages.IS_STANDALONE)); // Replaced by new ParentChildQueryTypes below queryTypes.Add(new XenModelObjectListContainsQueryType(5, ObjectTypes.None, PropertyNames.networks)); queryTypes.Add(new DiskQueryType(5, ObjectTypes.None, Messages.DISKS)); // Any new recursive ones should be added to GetSearchForResourceSelectButton() too queryTypes.Add(new RecursiveXMOQueryType(6, ObjectTypes.AllExcFolders, PropertyNames.pool, ObjectTypes.Pool)); queryTypes.Add(new RecursiveXMOListQueryType(6, ObjectTypes.AllExcFolders & ~ObjectTypes.Pool, PropertyNames.host, ObjectTypes.Server)); // i.e. everything except Pool queryTypes.Add(new RecursiveXMOListQueryType(6, ObjectTypes.AllExcFolders, PropertyNames.vm, ObjectTypes.VM)); queryTypes.Add(new RecursiveXMOListQueryType(6, ObjectTypes.Network | ObjectTypes.VM, PropertyNames.networks, ObjectTypes.Network)); queryTypes.Add(new RecursiveXMOListQueryType(6, ObjectTypes.LocalSR | ObjectTypes.RemoteSR | ObjectTypes.VM | ObjectTypes.VDI, PropertyNames.storage, ObjectTypes.LocalSR | ObjectTypes.RemoteSR)); queryTypes.Add(new RecursiveXMOListQueryType(6, ObjectTypes.VM | ObjectTypes.VDI, PropertyNames.disks, ObjectTypes.VDI)); queryTypes.Add(new RecursiveXMOQueryType(7, ObjectTypes.VM, PropertyNames.appliance, ObjectTypes.Appliance)); queryTypes.Add(new BooleanQueryType(7, ObjectTypes.VM, PropertyNames.in_any_appliance)); queryTypes.Add(new RecursiveXMOQueryType(8, ObjectTypes.AllIncFolders, PropertyNames.folder, ObjectTypes.Folder)); queryTypes.Add(new RecursiveXMOListQueryType(8, ObjectTypes.AllIncFolders, PropertyNames.folders, ObjectTypes.Folder)); queryTypes.Add(new NullQueryType(8, ObjectTypes.AllExcFolders, PropertyNames.folder, true, Messages.NOT_IN_A_FOLDER)); queryTypes.Add(new BooleanQueryType(9, ObjectTypes.AllExcFolders, PropertyNames.has_custom_fields)); OtherConfigAndTagsWatcher.OtherConfigChanged += OtherConfigWatcher_OtherConfigChanged; CustomFieldsManager.CustomFieldsChanged += OtherConfigWatcher_OtherConfigChanged; } private static void OtherConfigWatcher_OtherConfigChanged() { List customFieldDefinitions = CustomFieldsManager.GetCustomFields(); // Add new custom fields foreach (CustomFieldDefinition definition in customFieldDefinitions) { if (!CustomFieldExists(definition)) { if (definition.Type == CustomFieldDefinition.Types.String) customFields.Add(new CustomFieldStringQueryType(8, ObjectTypes.AllExcFolders, definition)); else customFields.Add(new CustomFieldDateQueryType(8, ObjectTypes.AllExcFolders, definition)); } } // Remove old ones List toRemove = new List(); foreach (CustomFieldQueryTypeBase customFieldQueryType in customFields) { if (!CustomFieldDefinitionExists(customFieldDefinitions, customFieldQueryType)) toRemove.Add(customFieldQueryType); } foreach (CustomFieldQueryTypeBase t in toRemove) { customFields.Remove(t); } } private static bool CustomFieldExists(CustomFieldDefinition definition) { return customFields.Exists(delegate(CustomFieldQueryTypeBase c) { return c.definition.Equals(definition); }); } private static bool CustomFieldDefinitionExists(List l, CustomFieldQueryTypeBase queryType) { return l.Exists(delegate(CustomFieldDefinition d) { return d.Equals(queryType.definition); }); } private readonly List subQueryElements; private QueryElement parentQueryElement; private Searcher searcher; private QueryScope queryScope; // Normally null, meaning use the scope from searcher (see WantQueryType). Set for the subquery of a parent-child query. private QueryFilter lastQueryFilter; public event Action QueryChanged; protected virtual void OnQueryChanged() { try { if (QueryChanged != null) QueryChanged(); } catch (Exception e) { log.Debug("Exception firing OnQueryChanged in QueryElement", e); } } public QueryElement() : this(null) { } public QueryElement(Searcher searcher, QueryScope queryScope, QueryElement parentQueryElement, QueryFilter query) : this(searcher, queryScope, parentQueryElement) { SelectQueryTypeFor(query); } public QueryElement(Searcher searcher, QueryScope queryScope, QueryElement parentQueryElement) : this(parentQueryElement) { this.Searcher = searcher; this.queryScope = queryScope; } private QueryElement(QueryElement parentQueryElement) // only use internally because it doesn't set searcher { subQueryElements = new List(); this.parentQueryElement = parentQueryElement; InitializeComponent(); queryTypeComboButton.BeforePopup += new EventHandler(queryTypeComboButton_BeforePopup); resourceSelectButton.BeforePopup += new EventHandler(resourceSelectButton_BeforePopup); queryTypeComboButton.SelectedItemChanged += OnQueryTypeComboButton_OnSelectedItemChanged; SelectDefaultQueryType(); Setup(); } private void OnQueryTypeComboButton_OnSelectedItemChanged(object sender, EventArgs e) { var selectedItem = queryTypeComboButton.SelectedItem; if (selectedItem.Tag is CustomFieldQueryTypeBase) { queryTypeComboButton.Text = selectedItem.ToString().Ellipsise(24); } } /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { CurrentQueryType = null; foreach (QueryElement subQueryElement in subQueryElements) { subQueryElement.Resize -= subQueryElement_Resize; subQueryElement.QueryChanged -= subQueryElement_QueryChanged; subQueryElement.searcher.SearchForChanged -= subQueryElement.searcher_SearchForChanged; subQueryElement.Dispose(); } if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } void queryTypeComboButton_BeforePopup(object sender, EventArgs e) { PopulateGroupTypesComboButton(false); } void resourceSelectButton_BeforePopup(object sender, EventArgs e) { PopulateResourceSelectButton(); } private QueryType CurrentQueryType { get { ToolStripItem selectedItem = queryTypeComboButton.SelectedItem; if (selectedItem == null) return null; return (QueryType)selectedItem.Tag; } set { if (CurrentQueryType != null) CurrentQueryType.SomeThingChanged -= queryType_SomeThingChanged; if (value == null) return; // We have to include all types in the drop-down temporarily, in case // we are deserializing from a type which is now forbidden (CA-25965). PopulateGroupTypesComboButton(true); queryTypeComboButton.SelectItem(delegate(QueryType queryType) { return queryType == value; }); PopulateGroupTypesComboButton(false); if (CurrentQueryType != null) { queryTypeComboButton.Text = CurrentQueryType.ToString(); CurrentQueryType.SomeThingChanged += queryType_SomeThingChanged; Setup(); } } } private bool IsSubQueryElement { get { return parentQueryElement != null; } } private void PopulateGroupTypesComboButton(bool showAll) { int queryTypeGroup = -1; queryTypeComboButton.ClearItems(); List allQueryTypes = new List(queryTypes); foreach (QueryType qt in customFields) allQueryTypes.Add(qt); // can't use AddRange() because customFields is List not List! foreach (QueryType queryType in allQueryTypes) { if (!showAll && !WantQueryType(queryType)) // only add relevant items to the drop-down continue; if (queryTypeGroup != -1 && queryType.Group != queryTypeGroup) { queryTypeComboButton.AddItem(new ToolStripSeparator()); } queryTypeGroup = queryType.Group; ToolStripMenuItem queryTypeMenuItem = new ToolStripMenuItem(); queryTypeMenuItem.Text = queryType.ToString(); queryTypeMenuItem.Tag = queryType; queryTypeComboButton.AddItem(queryTypeMenuItem); } } public void SelectDefaultQueryType() { CurrentQueryType = DefaultQueryType; } void queryType_SomeThingChanged(object sender, EventArgs e) { Program.Invoke(this, Setup); } private void RefreshSubQueryElements() { if (CurrentQueryType == null) return; QueryType.Category category = CurrentQueryType.GetCategory; int topOffset = queryTypeComboButton.Height + 2; foreach (QueryElement subQueryElement in subQueryElements) { subQueryElement.Resize -= new EventHandler(subQueryElement_Resize); subQueryElement.Resize += new EventHandler(subQueryElement_Resize); subQueryElement.QueryChanged -= subQueryElement_QueryChanged; subQueryElement.QueryChanged += subQueryElement_QueryChanged; if (category == QueryType.Category.ParentChild) { int indent = matchTypeComboButton.Left - queryTypeComboButton.Left; // need to push everything in the subQuery this far right subQueryElement.Left = indent; subQueryElement.Width = this.Width - indent; subQueryElement.Top = 0; } else // must be Category.Group { subQueryElement.Left = 30; subQueryElement.Width = this.Width - 30; subQueryElement.Top = topOffset; topOffset += subQueryElement.Height; } if (!Controls.Contains(subQueryElement)) Controls.Add(subQueryElement); } // This line causes OnResize(), which calls this function again recursively // (with the same "this"). I haven't messed with it yet because although this // function is called far more often than necessary, the number of query elements // is typically small, so it's not too painful. SRET 2009-01-09. this.Height = topOffset; } void subQueryElement_QueryChanged() { OnQueryChanged(); } private void ClearSubQueryElements() { foreach (QueryElement subQueryElement in subQueryElements.ToArray()) RemoveSubQueryElement(subQueryElement); } void subQueryElement_Resize(object sender, EventArgs e) { RefreshSubQueryElements(); if (parentQueryElement == null) Invalidate(); } protected override void OnResize(EventArgs e) { RefreshSubQueryElements(); // The following line shouldn't be necessary, but the right-anchoring doesn't seem to work // in one situation: when you have a parent-child query, and you change it into a group // query, so the parent-child query becomes the first child of the group, then the [-] button // appears in the wrong place. SRET 2009-01-09. removeButton.Left = this.Width - 25; base.OnResize(e); } private bool populatingComboButton = false; /// /// Populate a combo box, trying to preserve the selected index /// private void PopulateComboButton(DropDownComboButton comboButton, Object[] values, Func imageDelegate) { populatingComboButton = true; try { bool selected = false; ToolStripItem selectedItem = comboButton.SelectedItem; comboButton.ClearItems(); ToolStripMenuItem firstMenuItem = null; foreach (Object value in values) { ToolStripMenuItem menuItem = new ToolStripMenuItem(); String text = value.ToString(); menuItem.Text = text.EscapeAmpersands().Ellipsise(100); menuItem.Tag = value; if (imageDelegate != null) menuItem.Image = Images.GetImage16For(imageDelegate(value)); comboButton.AddItem(menuItem); if (firstMenuItem == null) firstMenuItem = menuItem; if (selectedItem != null && value.Equals(selectedItem.Tag)) { selected = true; comboButton.SelectedItem = menuItem; } } if (!selected) comboButton.SelectedItem = firstMenuItem; } finally { populatingComboButton = false; } } private void PopulateResourceSelectButton() { Search search = GetSearchForResourceSelectButton(); if (search == null) resourceSelectButton.ClearItems(); // shouldn't happen else resourceSelectButton.Populate(search); } private Search GetSearchForResourceSelectButton() { if (queryScope == null) return null; Query query = new Query(queryScope, null); Sort sort = new Sort("name", true); Sort[] sorts = { sort }; Grouping grouping = null; switch (queryScope.ObjectTypes) { // The same list of recursive types as defined in static QueryElement() case ObjectTypes.Pool: grouping = null; break; case ObjectTypes.Appliance: case ObjectTypes.Server: grouping = new XenModelObjectPropertyGrouping(PropertyNames.pool, null); break; case ObjectTypes.VM: case ObjectTypes.Network: case ObjectTypes.LocalSR | ObjectTypes.RemoteSR: { Grouping serverGrouping = new XenModelObjectPropertyGrouping(PropertyNames.host, null); grouping = new XenModelObjectPropertyGrouping(PropertyNames.pool, serverGrouping); break; } case ObjectTypes.VDI: { Grouping srGrouping = new XenModelObjectPropertyGrouping(PropertyNames.storage, null); Grouping serverGrouping = new XenModelObjectPropertyGrouping(PropertyNames.host, srGrouping); grouping = new XenModelObjectPropertyGrouping(PropertyNames.pool, serverGrouping); break; } case ObjectTypes.Folder: grouping = new FolderGrouping((Grouping)null); break; } return new Search(query, grouping, "", "", null, sorts); } private void Setup() { Setup(false); } private void Setup(bool queryTypeDropDownChanged) { if (populatingComboButton) return; // no remove button for "Select a filter", // nor for parent/child subqueries (where the main query already has a remove button on the same row) removeButton.Visible = !(CurrentQueryType is DummyQueryType) && !(IsSubQueryElement && parentQueryElement.CurrentQueryType.GetCategory == QueryType.Category.ParentChild); matchTypeComboButton.Visible = CurrentQueryType.ShowMatchTypeComboButton; if (CurrentQueryType.ShowMatchTypeComboButton) PopulateComboButton(matchTypeComboButton, CurrentQueryType.MatchTypeComboButtonEntries, null); QueryType.Category category = CurrentQueryType.GetCategory; if (category == QueryType.Category.Group) { // if we are changing from non-boolean to boolean, use the old query as the first child // of the new boolean query (CA-25127) if (queryTypeDropDownChanged && !(lastQueryFilter is GroupQuery)) { ClearSubQueryElements(); subQueryElements.Add(new QueryElement(this.Searcher, null, this, lastQueryFilter)); } } else if (category == QueryType.Category.ParentChild && queryTypeDropDownChanged) ClearSubQueryElements(); if (category != QueryType.Category.Single) { // if we don't have a "Select a filter...", then add one now if (!HasDummySubQuery()) AddDummySubQuery(CurrentQueryType.SubQueryScope); } else { ClearSubQueryElements(); } RefreshSubQueryElements(); // also if our parent doesn't have a "Select a filter...", then add one now // (This only occurs when changing from "Select a filter..." to something else). if (queryTypeDropDownChanged && lastQueryFilter is DummyQuery && IsSubQueryElement && parentQueryElement.CurrentQueryType is GroupQueryType && parentQueryElement.subQueryElements.Contains(this) && // this excludes the case where the subquery is still being initialized !parentQueryElement.HasDummySubQuery()) { parentQueryElement.AddDummySubQuery(null); parentQueryElement.RefreshSubQueryElements(); } Setup2(); } private void Setup2() { textBox.Visible = CurrentQueryType.ShowTextBox(this); dateTimePicker.Visible = CurrentQueryType.ShowDateTimePicker(this); unitsLabel.Visible = numericUpDown.Visible = CurrentQueryType.ShowNumericUpDown(this); unitsLabel.Text = CurrentQueryType.Units(this); ComboButton.Visible = CurrentQueryType.ShowComboButton(this); if (CurrentQueryType.ShowComboButton(this)) PopulateComboButton(ComboButton, CurrentQueryType.GetComboButtonEntries(this), null); resourceSelectButton.Visible = CurrentQueryType.ShowResourceSelectButton(this); if (CurrentQueryType.ShowResourceSelectButton(this)) PopulateResourceSelectButton(); OnQueryChanged(); } QueryFilter[] GetSubQueries() { List subQueries = new List(); foreach (QueryElement subQueryElement in subQueryElements) { QueryFilter query = subQueryElement.QueryFilter; if (query != null) subQueries.Add(query); } return subQueries.ToArray(); } private bool HasDummySubQuery() { foreach (QueryElement subQueryElement in subQueryElements) { if (subQueryElement.CurrentQueryType is DummyQueryType) return true; } return false; } private void AddDummySubQuery(QueryScope queryScope) { subQueryElements.Add(new QueryElement(this.Searcher, queryScope, this)); } public Searcher Searcher { get { return searcher; } set { searcher = value; if (searcher != null) searcher.SearchForChanged += searcher_SearchForChanged; } } public QueryFilter QueryFilter { get { return (CurrentQueryType == null ? null : CurrentQueryType.GetQuery(this)); } set { SelectQueryTypeFor(value); } } private void SelectQueryTypeFor(QueryFilter query) { if (query == null) { SelectDefaultQueryType(); return; } if (CurrentQueryType.ForQuery(query)) { Setup2(); CurrentQueryType.FromQuery(query, this); return; } foreach (QueryType queryType in queryTypes) { if (!queryType.ForQuery(query)) continue; CurrentQueryType = queryType; queryType.FromQuery(query, this); return; } foreach (QueryType queryType in customFields) { if (!queryType.ForQuery(query)) continue; CurrentQueryType = queryType; queryType.FromQuery(query, this); return; } SelectDefaultQueryType(); } // Do we want this query type, based on the search-for? private bool WantQueryType(QueryType qt) { QueryScope scope; bool recursive; // is this element the subquery of a parent-child query? if (queryScope != null) { scope = queryScope; recursive = true; } else { scope = (searcher == null ? null : searcher.QueryScope); recursive = false; } if (scope == null) return true; // If this is the subquery of a parent-child query, it can't have children if (recursive && qt.GetCategory != QueryType.Category.Single) return false; // Conversely, the UuidQueryType only shows up as the subquery of a parent-child query // (no point in a search for one object) if (!recursive && qt is UuidQueryType) return false; // If this is itself a parent-child query, we only allow it if the global scope // includes something other than this element's scope. For example, it's permissible // to search for Servers and VMs with a constraint on the Servers, but it's not // permissible to search for just Servers and put a constraint on the Servers. // Folders are an exception because the search is then for the parent folder, not // the folder itself. if (scope.WantSubsetOf(qt.SubQueryScope) && !qt.SubQueryScope.Equals(ObjectTypes.Folder)) return false; return scope.WantSubsetOf(qt.AppliesTo); } private void RemoveUnwantedFilters() { QueryType qt = CurrentQueryType; bool wanted = WantQueryType(qt); if (wanted && qt.GetCategory == QueryType.Category.Group) { // For group queries, remove subqueries recursively foreach (QueryElement subQueryElement in subQueryElements.ToArray()) subQueryElement.RemoveUnwantedFilters(); // This element is now unwanted if we've deleted all its children if (subQueryElements.Count == 0) wanted = false; // If this is an AND or OR (not a NONE), and there is only one // child left, remove the conjunction by promoting the child. if (subQueryElements.Count == 1) { GroupQueryType gqt = qt as GroupQueryType; if (gqt != null && (gqt.Type == GroupQuery.GroupQueryType.And || gqt.Type == GroupQuery.GroupQueryType.Or)) { this.QueryFilter = subQueryElements[0].QueryFilter; RefreshSubQueryElements(); } } } if (!wanted) { if (IsSubQueryElement) { parentQueryElement.RemoveSubQueryElement(this); parentQueryElement.RefreshSubQueryElements(); } else SelectDefaultQueryType(); } } private void searcher_SearchForChanged() { // We only run this for the outermost query because subqueries // are removed by their parent (RemoveUnwantedFilters() is recursive). if (!IsSubQueryElement) { RemoveUnwantedFilters(); Setup(); OnQueryChanged(); } } private void queryTypeComboButton_BeforeItemSelected(object sender, EventArgs e) { lastQueryFilter = this.QueryFilter; if (CurrentQueryType != null) { CurrentQueryType.SomeThingChanged -= queryType_SomeThingChanged; } } private void queryTypeComboButton_ItemSelected(object sender, EventArgs e) { if (CurrentQueryType != null) { CurrentQueryType.SomeThingChanged += queryType_SomeThingChanged; } } private void queryTypeComboButton_SelectedItemChanged(object sender, EventArgs e) { Setup(true); } private void matchTypeComboButton_SelectedItemChanged(object sender, EventArgs e) { Setup2(); } private void ComboButton_SelectedIndexChanged(object sender, EventArgs e) { OnQueryChanged(); } private void resourceSelectButton_SelectedIndexChanged(object sender, EventArgs e) { OnQueryChanged(); } private void numericUpDown_ValueChanged(object sender, EventArgs e) { OnQueryChanged(); } private void numericUpDown_KeyUp(object sender, KeyEventArgs e) { OnQueryChanged(); } private void dateTimePicker_ValueChanged(object sender, EventArgs e) { OnQueryChanged(); } private void textBox_TextChanged(object sender, EventArgs e) { OnQueryChanged(); } private void removeButton_Click(object sender, EventArgs e) { if (IsSubQueryElement) { parentQueryElement.RemoveSubQueryElement(this); parentQueryElement.RefreshSubQueryElements(); parentQueryElement.OnQueryChanged(); } else { SelectDefaultQueryType(); OnQueryChanged(); } } // Remove subquery element. Caller needs to call parent.RefreshSubQueryElements() and // parent.OnQueryChanged() -- or parent.Setup() which calls both -- afterwards. private void RemoveSubQueryElement(QueryElement subQueryElement) { Controls.Remove(subQueryElement); subQueryElements.Remove(subQueryElement); subQueryElement.Resize -= subQueryElement_Resize; subQueryElement.QueryChanged -= subQueryElement_QueryChanged; subQueryElement.searcher.SearchForChanged -= subQueryElement.searcher_SearchForChanged; subQueryElement.Dispose(); } #region Nested classes internal abstract class QueryType { private readonly int group = 0; private readonly ObjectTypes appliesTo; protected QueryType(int group, ObjectTypes appliesTo) { this.group = group; this.appliesTo = appliesTo; } /// /// If this query type is correct for this query, return true; /// Call FromQuery to populate the correct combo boxes etc /// /// /// public abstract bool ForQuery(QueryFilter query); public abstract void FromQuery(QueryFilter query, QueryElement queryElement); /// /// May return null if this query element is not ready yet /// public abstract QueryFilter GetQuery(QueryElement queryElement); public enum Category { Single, // match the object itself ParentChild, // match ancestor or descendant object Group // a group query } public virtual Category GetCategory { get { return Category.Single; } } public virtual QueryScope SubQueryScope { get { return null; } } // QueryScope for subquery (if category == ParentChild) public virtual bool ShowMatchTypeComboButton { get { return false; } } public virtual Object[] MatchTypeComboButtonEntries { get { return null; } } public virtual bool ShowTextBox(QueryElement queryElement) { return false; } public virtual bool ShowDateTimePicker(QueryElement queryElement) { return false; } public virtual bool ShowComboButton(QueryElement queryElement) { return false; } public virtual bool ShowNumericUpDown(QueryElement queryElement) { return false; } public virtual bool ShowResourceSelectButton(QueryElement queryElement) { return false; } public virtual String Units(QueryElement queryElement) { return String.Empty; } public virtual Object[] GetComboButtonEntries(QueryElement queryElement) { return null; } public event EventHandler SomeThingChanged; public int Group { get { return group; } } public ObjectTypes AppliesTo { get { return appliesTo; } } /// /// "Something changed" means that some external thing changed, usually in the cache, /// and we need to repopulate the drop-downs for this QueryElement as a result. /// protected void OnSomeThingChanged() { if (SomeThingChanged != null) SomeThingChanged(this, new EventArgs()); } } // A dummy query type which doesn't contribute to the search internal class DummyQueryType : QueryType { internal DummyQueryType(int group, ObjectTypes appliesTo) : base(group, appliesTo) { } public override String ToString() { return Messages.SELECT_A_FILTER; } public override bool ForQuery(QueryFilter query) { return (query is DummyQuery); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { } public override QueryFilter GetQuery(QueryElement queryElement) { return new DummyQuery(); } } internal class GroupQueryType : QueryType { private readonly GroupQuery.GroupQueryType type; internal GroupQueryType(int group, ObjectTypes appliesTo, GroupQuery.GroupQueryType type) : base(group, appliesTo) { this.type = type; } public GroupQuery.GroupQueryType Type { get { return type; } } public override String ToString() { switch (type) { case GroupQuery.GroupQueryType.And: return Messages.ALL_OF; case GroupQuery.GroupQueryType.Or: return Messages.ANY_OF; case GroupQuery.GroupQueryType.Nor: return Messages.NONE_OF; default: return ""; } } public override Category GetCategory { get { return Category.Group; } } public override QueryFilter GetQuery(QueryElement queryElement) { return new GroupQuery(queryElement.GetSubQueries(), type); } public override bool ForQuery(QueryFilter query) { GroupQuery groupQuery = query as GroupQuery; if (groupQuery == null) return false; if (groupQuery.type != type) return false; return true; } public override void FromQuery(QueryFilter query, QueryElement queryElement) { GroupQuery groupQuery = query as GroupQuery; for (int i = 0; i < groupQuery.subqueries.Length; i++) { QueryFilter subquery = groupQuery.subqueries[i]; if (queryElement.subQueryElements.Count > i) queryElement.subQueryElements[i].SelectQueryTypeFor(subquery); else queryElement.subQueryElements.Add(new QueryElement(queryElement.Searcher, null, queryElement, subquery)); } while (queryElement.subQueryElements.Count > groupQuery.subqueries.Length) queryElement.RemoveSubQueryElement( queryElement.subQueryElements[queryElement.subQueryElements.Count - 1]); queryElement.Setup(); } } internal abstract class PropertyQueryType : QueryType { protected readonly PropertyNames property; protected readonly Func propertyAccessor; protected readonly String i18n; protected PropertyQueryType(int group, ObjectTypes appliesTo, PropertyNames property, String i18n) : base(group, appliesTo) { this.property = property; this.propertyAccessor = PropertyAccessors.Get(property); this.i18n = (i18n == null ? PropertyAccessors.PropertyNames_i18n[property] : i18n); } protected PropertyQueryType(int group, ObjectTypes appliesTo, PropertyNames property) : this(group, appliesTo, property, null) { } public override String ToString() { return i18n; } // Note: This function only tests the values of T and property. // This is good enough for most cases and avoids each derived class // having to define its own ForQuery. But in some cases there are // clashes (particularly when T is XMO or List>) and in // that case we do have to override this function in both derived // classes. Derived classes of MatchType may lead to clashes too. public override bool ForQuery(QueryFilter query) { PropertyQuery propertyQuery = query as PropertyQuery; if (propertyQuery == null) return false; return (propertyQuery.property == this.property); } } internal class StringPropertyQueryType : PropertyQueryType { internal StringPropertyQueryType(int group, ObjectTypes appliesTo, PropertyNames property) : base(group, appliesTo, property) { } public override bool ShowMatchTypeComboButton { get { return true; } } public override bool ShowTextBox(QueryElement queryElement) { return true; } public class ExtraComboEntry { public readonly StringPropertyQuery.PropertyQueryType type; public ExtraComboEntry(StringPropertyQuery.PropertyQueryType type) { this.type = type; } public override string ToString() { switch (type) { case StringPropertyQuery.PropertyQueryType.contains: return Messages.CONTAINS; case StringPropertyQuery.PropertyQueryType.exactmatch: return Messages.EXACT_MATCH; case StringPropertyQuery.PropertyQueryType.endswith: return Messages.ENDS_WITH; case StringPropertyQuery.PropertyQueryType.startswith: return Messages.STARTS_WITH; case StringPropertyQuery.PropertyQueryType.notcontain: return Messages.NOT_CONTAINS; default: return ""; } } public override bool Equals(object obj) { ExtraComboEntry other = obj as ExtraComboEntry; if (other == null) return false; return type == other.type; } public override int GetHashCode() { return (int) type; } } public override object[] MatchTypeComboButtonEntries { get { return MatchTypeEntries; } } public static object[] MatchTypeEntries { get { return new ExtraComboEntry[] { new ExtraComboEntry(StringPropertyQuery.PropertyQueryType.contains), new ExtraComboEntry(StringPropertyQuery.PropertyQueryType.notcontain), new ExtraComboEntry(StringPropertyQuery.PropertyQueryType.startswith), new ExtraComboEntry(StringPropertyQuery.PropertyQueryType.endswith), new ExtraComboEntry(StringPropertyQuery.PropertyQueryType.exactmatch) }; } } public override QueryFilter GetQuery(QueryElement queryElement) { ExtraComboEntry extraComboEntry = (ExtraComboEntry)queryElement.matchTypeComboButton.SelectedItem.Tag; return new StringPropertyQuery(property, queryElement.textBox.Text, extraComboEntry.type, false); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { StringPropertyQuery stringQuery = query as StringPropertyQuery; queryElement.matchTypeComboButton.SelectItem(delegate(ExtraComboEntry entry) { return entry.type == stringQuery.type; }); queryElement.textBox.Text = stringQuery.query; } } // The object "Is" query, selected from a list of objects. // Internally implemented using UUIDs. internal class UuidQueryType : PropertyQueryType { internal UuidQueryType(int group, ObjectTypes appliesTo) : base(group, appliesTo, PropertyNames.uuid, Messages.UUID_SEARCH) { } public override bool ShowResourceSelectButton(QueryElement queryElement) { return true; } public override QueryFilter GetQuery(QueryElement queryElement) { ToolStripItem item = queryElement.resourceSelectButton.SelectedItem; IXenObject ixmo = (item == null ? null : item.Tag as IXenObject); String uuid = (string)propertyAccessor(ixmo); return new StringPropertyQuery(property, uuid, StringPropertyQuery.PropertyQueryType.exactmatch, true); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { StringPropertyQuery stringQuery = query as StringPropertyQuery; queryElement.resourceSelectButton.SelectItem(delegate(IXenObject entry) { return ((string)propertyAccessor(entry) == stringQuery.query); }); } } // Search for UUID as a string internal class UuidStringQueryType: StringPropertyQueryType { internal UuidStringQueryType(int group, ObjectTypes appliesTo) : base(group, appliesTo, PropertyNames.uuid) { } public override object[] MatchTypeComboButtonEntries { get { return new ExtraComboEntry[] { new ExtraComboEntry(StringPropertyQuery.PropertyQueryType.startswith), new ExtraComboEntry(StringPropertyQuery.PropertyQueryType.exactmatch) }; } } } internal class IPAddressQueryType : PropertyQueryType> { internal IPAddressQueryType(int group, ObjectTypes appliesTo, PropertyNames property) : base(group, appliesTo, property) { } public override bool ShowMatchTypeComboButton { get { return true; } } public override bool ShowTextBox(QueryElement queryElement) { return true; } public override Object[] MatchTypeComboButtonEntries { get { return new String[] { Messages.IS }; } } public override QueryFilter GetQuery(QueryElement queryElement) { ComparableAddress address; if (!ComparableAddress.TryParse(queryElement.textBox.Text, true, true, out address)) return null; return new IPAddressQuery(property, address); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { IPAddressQuery ipAddressQuery = query as IPAddressQuery; queryElement.textBox.Text = ipAddressQuery.address.ToString(); } } internal class DatePropertyQueryType : PropertyQueryType { internal DatePropertyQueryType(int group, ObjectTypes appliesTo, PropertyNames property) : base(group, appliesTo, property) { } public override bool ShowMatchTypeComboButton { get { return true; } } public override bool ShowDateTimePicker(QueryElement queryElement) { return ShouldShowDateTimePicker(queryElement); } public static bool ShouldShowDateTimePicker(QueryElement queryElement) { ExtraComboEntry extraComboEntry = (ExtraComboEntry)queryElement.matchTypeComboButton.SelectedItem.Tag; switch (extraComboEntry.type) { case DatePropertyQuery.PropertyQueryType.before: case DatePropertyQuery.PropertyQueryType.after: case DatePropertyQuery.PropertyQueryType.exact: return true; default: return false; } } internal class ExtraComboEntry { public readonly DatePropertyQuery.PropertyQueryType type; public ExtraComboEntry(DatePropertyQuery.PropertyQueryType type) { this.type = type; } public override string ToString() { switch (type) { case DatePropertyQuery.PropertyQueryType.after: return Messages.AFTER; case DatePropertyQuery.PropertyQueryType.before: return Messages.BEFORE; case DatePropertyQuery.PropertyQueryType.exact: return Messages.EXACT_MATCH; case DatePropertyQuery.PropertyQueryType.lastweek: return Messages.LAST_WEEK; case DatePropertyQuery.PropertyQueryType.thisweek: return Messages.THIS_WEEK; case DatePropertyQuery.PropertyQueryType.today: return Messages.TODAY; case DatePropertyQuery.PropertyQueryType.yesterday: return Messages.YESTERDAY; default: return ""; } } public override bool Equals(object obj) { ExtraComboEntry other = obj as ExtraComboEntry; if (other == null) return false; return type == other.type; } public override int GetHashCode() { return (int)type; } } public override object[] MatchTypeComboButtonEntries { get { return MatchTypeEntries; } } public static object[] MatchTypeEntries { get { return new ExtraComboEntry[] { new ExtraComboEntry(DatePropertyQuery.PropertyQueryType.today), new ExtraComboEntry(DatePropertyQuery.PropertyQueryType.yesterday), new ExtraComboEntry(DatePropertyQuery.PropertyQueryType.thisweek), new ExtraComboEntry(DatePropertyQuery.PropertyQueryType.lastweek), new ExtraComboEntry(DatePropertyQuery.PropertyQueryType.before), new ExtraComboEntry(DatePropertyQuery.PropertyQueryType.after), new ExtraComboEntry(DatePropertyQuery.PropertyQueryType.exact) }; } } public override QueryFilter GetQuery(QueryElement queryElement) { ExtraComboEntry extraComboEntry = (ExtraComboEntry)queryElement.matchTypeComboButton.SelectedItem.Tag; return new DatePropertyQuery(property, queryElement.dateTimePicker.Value, extraComboEntry.type); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { DatePropertyQuery dateQuery = query as DatePropertyQuery; queryElement.matchTypeComboButton.SelectItem(delegate(ExtraComboEntry entry) { return entry.type == dateQuery.type; }); queryElement.dateTimePicker.Value = dateQuery.query; } } internal class EnumPropertyQueryType : PropertyQueryType { private readonly Dictionary i18ns; internal EnumPropertyQueryType(int group, ObjectTypes appliesTo, PropertyNames property) : base(group, appliesTo, property) { this.i18ns = (Dictionary)PropertyAccessors.Geti18nFor(property); System.Diagnostics.Trace.Assert(typeof(T).IsEnum); } public override bool ShowMatchTypeComboButton { get { return true; } } public override Object[] MatchTypeComboButtonEntries { get { return new String[] { Messages.IS, Messages.IS_NOT }; } } public override bool ShowComboButton(QueryElement queryElement) { return true; } public override Object[] GetComboButtonEntries(QueryElement queryElement) { String[] enumStrings = new String[i18ns.Count]; i18ns.Keys.CopyTo(enumStrings, 0); return enumStrings; } public override QueryFilter GetQuery(QueryElement queryElement) { bool equals = queryElement.matchTypeComboButton.SelectedItem.Tag.Equals(Messages.IS); T query = i18ns[(String)queryElement.ComboButton.SelectedItem.Tag]; return new EnumPropertyQuery(property, query, equals); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { EnumPropertyQuery enumQuery = query as EnumPropertyQuery; if (enumQuery == null) // This should never happen, but is the return; // only cause of CA-18166 reopening queryElement.matchTypeComboButton.SelectItem(delegate(String entry) { return entry == (enumQuery.equals ? Messages.IS : Messages.IS_NOT); }); queryElement.ComboButton.SelectItem(delegate(String entry) { return Enum.Equals(i18ns[entry], enumQuery.query); }); } } // This class is currently only used for the OS, which is only available for VMs. // If this changes, we may need to alter the loop in populateCollectedValues() to // iterate over other objects. SRET 2010-11-01. internal class ValuePropertyQueryType : PropertyQueryType { private readonly Dictionary collectedValues; public ValuePropertyQueryType(int group, ObjectTypes appliesTo, PropertyNames property) : base(group, appliesTo, property) { collectedValues = new Dictionary(); ConnectionsManager.XenConnections.CollectionChanged += CollectionChanged; populateCollectedValues(); } // This object is static for the life of the GUI, so we never have to unhook these events. private void populateCollectedValues() { collectedValues.Clear(); foreach (IXenConnection connection in ConnectionsManager.XenConnectionsCopy) { connection.Cache.RegisterBatchCollectionChanged(CollectionChanged); // Only iterate over VMs: see comments at the top of this class. foreach (VM vm in connection.Cache.VMs) { String value = propertyAccessor(vm) as String; if (value == null) continue; if (collectedValues.ContainsKey(value)) continue; collectedValues[value] = null; } } OnSomeThingChanged(); } void CollectionChanged(object sender, EventArgs e) { Program.BeginInvoke(Program.MainWindow,populateCollectedValues); } public override bool ShowMatchTypeComboButton { get { return true; } } public override Object[] MatchTypeComboButtonEntries { get { return new String[] { Messages.IS, Messages.IS_NOT }; } } public override bool ShowComboButton(QueryElement queryElement) { return true; } public override Object[] GetComboButtonEntries(QueryElement queryElement) { String[] entries = new String[collectedValues.Keys.Count]; collectedValues.Keys.CopyTo(entries, 0); // Sort the list in alphabetical order, but put "Unknown" at the bottom Array.Sort(entries, (a, b) => { if (a == Messages.UNKNOWN) return (b == Messages.UNKNOWN ? 0 : 1); else if (b == Messages.UNKNOWN) return -1; else return a.CompareTo(b); } ); return entries; } public override QueryFilter GetQuery(QueryElement queryElement) { if (queryElement.ComboButton.SelectedItem == null) return null; bool equals = queryElement.matchTypeComboButton.SelectedItem.Tag.Equals(Messages.IS); String query = queryElement.ComboButton.SelectedItem.Tag as String; return new ValuePropertyQuery(property, query, equals); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { ValuePropertyQuery valueQuery = query as ValuePropertyQuery; queryElement.matchTypeComboButton.SelectItem(delegate(String entry) { return entry == (valueQuery.equals ? Messages.IS : Messages.IS_NOT); }); queryElement.ComboButton.SelectItem(delegate(String entry) { return entry.Equals(valueQuery.query); }); } } internal abstract class RecursiveQueryType : PropertyQueryType where O : XenObject where Q : RecursivePropertyQuery { QueryScope subQueryScope; public RecursiveQueryType(int group, ObjectTypes appliesTo, PropertyNames property, ObjectTypes subQueryScope) : base(group, appliesTo, property) { this.subQueryScope = new QueryScope(subQueryScope); ConnectionsManager.XenConnections.CollectionChanged += XenConnections_CollectionChanged; XenConnections_CollectionChanged(null, null); } public override Category GetCategory { get { return Category.ParentChild; } } public override QueryScope SubQueryScope { get { return subQueryScope; } } void XenConnections_CollectionChanged(object sender, CollectionChangeEventArgs e) { foreach (IXenConnection connection in ConnectionsManager.XenConnectionsCopy) { connection.Cache.RegisterBatchCollectionChanged(dict_BatchCollectionChanged); } } void dict_BatchCollectionChanged(object sender, EventArgs e) { OnSomeThingChanged(); } protected abstract RecursivePropertyQuery newQuery(PropertyNames property, QueryFilter qf); public override QueryFilter GetQuery(QueryElement queryElement) { QueryFilter qf; QueryFilter[] subQueries = queryElement.GetSubQueries(); if (subQueries == null || subQueries.Length == 0) // shouldn't happen, but let's be safe qf = new DummyQuery(); else qf = subQueries[0]; // Special case for CA-30595: interpret "parent folder is (blank)" as "parent folder is /". if (property == PropertyNames.folder && qf is StringPropertyQuery && (qf as StringPropertyQuery).property == PropertyNames.uuid && (qf as StringPropertyQuery).query == null) { qf = new StringPropertyQuery(PropertyNames.uuid, "/", StringPropertyQuery.PropertyQueryType.exactmatch, true); } return newQuery(property, qf); // this should just be new Q(property, qf) but that's illegal syntax: can only use default constructor because there's no way to specify that all derived classes must implement new(property, qf) } public override bool ForQuery(QueryFilter query) { RecursivePropertyQuery recursivePropertyQuery = query as RecursivePropertyQuery; return (recursivePropertyQuery != null && recursivePropertyQuery.property == property); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { Q recursiveQuery = query as Q; QueryFilter subQuery = recursiveQuery.subQuery; queryElement.ClearSubQueryElements(); queryElement.subQueryElements.Add(new QueryElement(queryElement.Searcher, SubQueryScope, queryElement, subQuery)); queryElement.Setup(); } } internal class RecursiveXMOQueryType : RecursiveQueryType> where T : XenObject { public RecursiveXMOQueryType(int group, ObjectTypes appliesTo, PropertyNames property, ObjectTypes subQueryScope) : base(group, appliesTo, property, subQueryScope) { } protected override RecursivePropertyQuery newQuery(PropertyNames property, QueryFilter qf) { return new RecursiveXMOPropertyQuery(property, qf); } } internal class RecursiveXMOListQueryType : RecursiveQueryType, T, RecursiveXMOListPropertyQuery> where T : XenObject { public RecursiveXMOListQueryType(int group, ObjectTypes appliesTo, PropertyNames property, ObjectTypes subQueryScope) : base(group, appliesTo, property, subQueryScope) { } protected override RecursivePropertyQuery> newQuery(PropertyNames property, QueryFilter qf) { return new RecursiveXMOListPropertyQuery(property, qf); } } // NOT USED FOR NEW QUERIES internal class XenModelObjectPropertyQueryType : PropertyQueryType where T : XenObject { public XenModelObjectPropertyQueryType(int group, ObjectTypes appliesTo, PropertyNames property) : base(group, appliesTo, property) { ConnectionsManager.XenConnections.CollectionChanged += XenConnections_CollectionChanged; XenConnections_CollectionChanged(null, null); } void XenConnections_CollectionChanged(object sender, CollectionChangeEventArgs e) { foreach (IXenConnection connection in ConnectionsManager.XenConnectionsCopy) { connection.Cache.RegisterBatchCollectionChanged(dict_BatchCollectionChanged); } } void dict_BatchCollectionChanged(object sender, EventArgs e) { OnSomeThingChanged(); } public override bool ShowMatchTypeComboButton { get { return true; } } public override Object[] MatchTypeComboButtonEntries { get { return new String[] { Messages.IS, Messages.IS_NOT }; } } public override bool ShowComboButton(QueryElement queryElement) { return true; } public override Object[] GetComboButtonEntries(QueryElement queryElement) { List entries = new List(); foreach (IXenConnection connection in ConnectionsManager.XenConnectionsCopy) { connection.Cache.AddAll(entries, null); } // CA-17132: Special case pools because they're silly if (typeof(T) == typeof(Pool)) { entries.RemoveAll((Predicate)delegate(T m) { return Helpers.GetPool(m.Connection) == null; }); } return entries.ToArray(); } public override QueryFilter GetQuery(QueryElement queryElement) { ToolStripItem i = queryElement.ComboButton.SelectedItem; if (i == null || !(i.Tag is T)) return null; bool equals = queryElement.matchTypeComboButton.SelectedItem.Tag.Equals(Messages.IS); T query = (T)i.Tag; return new XenModelObjectPropertyQuery(property, query, equals); } public override bool ForQuery(QueryFilter query) { XenModelObjectPropertyQuery xmoPropertyQuery = query as XenModelObjectPropertyQuery; if (xmoPropertyQuery == null) return false; return (xmoPropertyQuery.property == this.property); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { XenModelObjectPropertyQuery xmoPropertyQuery = query as XenModelObjectPropertyQuery; queryElement.matchTypeComboButton.SelectItem(delegate(String entry) { return entry == (xmoPropertyQuery.equals ? Messages.IS : Messages.IS_NOT); }); queryElement.ComboButton.SelectItem(delegate(T entry) { return entry.opaque_ref.Equals(xmoPropertyQuery.query.opaque_ref); }); } } // NOT USED FOR NEW QUERIES internal class XenModelObjectListContainsQueryType : PropertyQueryType> where T : XenObject { public XenModelObjectListContainsQueryType(int group, ObjectTypes appliesTo, PropertyNames property) : base(group, appliesTo, property) { ConnectionsManager.XenConnections.CollectionChanged += XenConnections_CollectionChanged; XenConnections_CollectionChanged(null, null); } void XenConnections_CollectionChanged(object sender, CollectionChangeEventArgs e) { foreach (IXenConnection connection in ConnectionsManager.XenConnectionsCopy) { connection.Cache.RegisterBatchCollectionChanged(dict_BatchCollectionChanged); } } void dict_BatchCollectionChanged(object sender, EventArgs e) { OnSomeThingChanged(); } public override bool ShowMatchTypeComboButton { get { return true; } } public override Object[] MatchTypeComboButtonEntries { get { if (property == PropertyNames.networks) return new String[] { Messages.USES, Messages.DOES_NOT_USE, Messages.STARTS_WITH, Messages.ENDS_WITH, Messages.CONTAINS, Messages.NOT_CONTAINS }; return new String[] { Messages.IS, Messages.IS_NOT, Messages.STARTS_WITH, Messages.ENDS_WITH, Messages.CONTAINS, Messages.NOT_CONTAINS }; } } public override bool ShowComboButton(QueryElement queryElement) { String matchType = queryElement.matchTypeComboButton.SelectedItem.Tag as String; return matchType == Messages.USES || matchType == Messages.CONTAINED_IN || matchType == Messages.IS || matchType == Messages.DOES_NOT_USE || matchType == Messages.NOT_CONTAINED_IN || matchType == Messages.IS_NOT; } public override Object[] GetComboButtonEntries(QueryElement queryElement) { List entries = new List(); foreach (IXenConnection connection in ConnectionsManager.XenConnectionsCopy) { connection.Cache.AddAll(entries, null); } // CA-24314: Filter networks that should be hidden if (typeof(T) == typeof(XenAPI.Network)) { foreach (T entry in entries.ToArray()) { XenAPI.Network e = entry as XenAPI.Network; if (!e.Show(XenAdmin.Properties.Settings.Default.ShowHiddenVMs)) entries.Remove(entry); } } return entries.ToArray(); } public override bool ShowTextBox(QueryElement queryElement) { return !ShowComboButton(queryElement); } public override QueryFilter GetQuery(QueryElement queryElement) { String matchType = queryElement.matchTypeComboButton.SelectedItem.Tag as String; if (ShowComboButton(queryElement)) { T t = queryElement.ComboButton.SelectedItem == null ? null : queryElement.ComboButton.SelectedItem.Tag as T; if (matchType == Messages.USES || matchType == Messages.CONTAINED_IN || matchType == Messages.IS) { return new XenModelObjectListContainsQuery(property, t, true); } else { System.Diagnostics.Trace.Assert(matchType == Messages.DOES_NOT_USE || matchType == Messages.NOT_CONTAINED_IN || matchType == Messages.IS_NOT); return new XenModelObjectListContainsQuery(property, t, false); } } else { String query = queryElement.textBox.Text; if (matchType == Messages.STARTS_WITH) { return new XenModelObjectListContainsNameQuery(property, StringPropertyQuery.PropertyQueryType.startswith, query); } else if (matchType == Messages.ENDS_WITH) { return new XenModelObjectListContainsNameQuery(property, StringPropertyQuery.PropertyQueryType.endswith, query); } else if (matchType == Messages.CONTAINS) { return new XenModelObjectListContainsNameQuery(property, StringPropertyQuery.PropertyQueryType.contains, query); } else if (matchType == Messages.NOT_CONTAINS) { return new XenModelObjectListContainsNameQuery(property, StringPropertyQuery.PropertyQueryType.notcontain, query); } else { return null; } } } public override bool ForQuery(QueryFilter query) { ListContainsQuery listQuery = query as ListContainsQuery; // includes both XenModelObjectListContainsQuery and XenModelObjectListContainsNameQuery if (listQuery == null) return false; return (listQuery.property == this.property); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { XenModelObjectListContainsQuery listContainsQuery = query as XenModelObjectListContainsQuery; if (listContainsQuery == null) { XenModelObjectListContainsNameQuery nameQuery = query as XenModelObjectListContainsNameQuery; queryElement.matchTypeComboButton.SelectItem(delegate(String entry) { if (entry == Messages.STARTS_WITH) { return nameQuery.type == StringPropertyQuery.PropertyQueryType.startswith; } else if (entry == Messages.ENDS_WITH) { return nameQuery.type == StringPropertyQuery.PropertyQueryType.endswith; } else if (entry == Messages.CONTAINS) { return nameQuery.type == StringPropertyQuery.PropertyQueryType.contains; } else if (entry == Messages.NOT_CONTAINS) { return nameQuery.type == StringPropertyQuery.PropertyQueryType.notcontain; } return false; }); queryElement.textBox.Text = nameQuery.query; } else { queryElement.matchTypeComboButton.SelectItem(delegate(String entry) { if (entry == Messages.USES || entry == Messages.CONTAINED_IN || entry == Messages.IS) { return listContainsQuery.contains; } else if (entry == Messages.DOES_NOT_USE || entry == Messages.NOT_CONTAINED_IN || entry == Messages.IS_NOT) { return !listContainsQuery.contains; } return false; }); queryElement.ComboButton.SelectItem(delegate(T entry) { return Helpers.GetUuid(entry).Equals(listContainsQuery.queryUUID); }); } } } // NOT USED FOR NEW QUERIES internal class HostQueryType : XenModelObjectListContainsQueryType { public HostQueryType(int group, ObjectTypes appliesTo) : base(group, appliesTo, PropertyNames.host) { } public override Object[] MatchTypeComboButtonEntries { get { List matchTypes = new List(base.MatchTypeComboButtonEntries); matchTypes.Add(Messages.IS_STANDALONE); matchTypes.Add(Messages.IS_IN_A_POOL); return matchTypes.ToArray(); } } public override bool ShowTextBox(QueryElement queryElement) { if (ShowComboButton(queryElement)) return false; String matchType = queryElement.matchTypeComboButton.SelectedItem.Tag as String; return matchType != Messages.IS_STANDALONE && matchType != Messages.IS_IN_A_POOL; } public override QueryFilter GetQuery(QueryElement queryElement) { if (ShowComboButton(queryElement) || ShowTextBox(queryElement)) return base.GetQuery(queryElement); String matchType = queryElement.matchTypeComboButton.SelectedItem.Tag as String; return new NullQuery(PropertyNames.pool, matchType == Messages.IS_STANDALONE); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { NullQuery nullQuery = query as NullQuery; if (nullQuery != null) { queryElement.matchTypeComboButton.SelectItem(delegate(String entry) { return entry == Messages.IS_STANDALONE == nullQuery.query; }); } else { base.FromQuery(query, queryElement); } } } internal class TagQueryType : PropertyQueryType> { private String[] tags = new String[] { }; private String deletedTag = null; public TagQueryType(int group, ObjectTypes appliesTo) : base(group, appliesTo, PropertyNames.tags) { OtherConfigAndTagsWatcher.TagsChanged += OtherConfigAndTagsWatcher_TagsChanged; } void OtherConfigAndTagsWatcher_TagsChanged() { tags = Tags.GetAllTags(); OnSomeThingChanged(); } public override bool ShowMatchTypeComboButton { get { return true; } } public override Object[] MatchTypeComboButtonEntries { get { return new String[] { Messages.CONTAINS, Messages.NOT_CONTAINS, Messages.ARE_EMPTY, Messages.ARE_NOT_EMPTY }; } } public override bool ShowComboButton(QueryElement queryElement) { String matchType = queryElement.matchTypeComboButton.SelectedItem.Tag as String; return matchType == Messages.CONTAINS || matchType == Messages.NOT_CONTAINS; } public override Object[] GetComboButtonEntries(QueryElement queryElement) { if (deletedTag != null) { List newTags = new List(tags); newTags.Add(deletedTag); return newTags.ToArray(); } return tags; } public override QueryFilter GetQuery(QueryElement queryElement) { if (!ShowComboButton(queryElement)) { String matchType = queryElement.matchTypeComboButton.SelectedItem.Tag as String; return new ListEmptyQuery(property, matchType == Messages.ARE_EMPTY); } if (queryElement.ComboButton.SelectedItem == null) return null; bool contains = queryElement.matchTypeComboButton.SelectedItem.Tag.Equals(Messages.CONTAINS); String query = queryElement.ComboButton.SelectedItem.Tag as String; return new StringListContainsQuery(property, query, contains); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { StringListContainsQuery listContainsQuery = query as StringListContainsQuery; if (listContainsQuery != null) { if (Array.BinarySearch(tags, listContainsQuery.query) < 0) { deletedTag = listContainsQuery.query; OnSomeThingChanged(); } queryElement.matchTypeComboButton.SelectItem(delegate(String entry) { return entry == (listContainsQuery.contains ? Messages.CONTAINS : Messages.NOT_CONTAINS); }); queryElement.ComboButton.SelectItem(delegate(String entry) { return entry.Equals(listContainsQuery.query); }); return; } ListEmptyQuery listEmptyQuery = query as ListEmptyQuery; if (listEmptyQuery != null) { queryElement.matchTypeComboButton.SelectItem(delegate(String entry) { return entry == (listEmptyQuery.empty ? Messages.ARE_EMPTY : Messages.ARE_NOT_EMPTY); }); } } } internal abstract class CustomFieldQueryTypeBase : QueryType { public readonly CustomFieldDefinition definition; public CustomFieldQueryTypeBase(int group, ObjectTypes appliesTo, CustomFieldDefinition definition) : base(group, appliesTo) { this.definition = definition; } public override string ToString() { return definition.Name; } public override bool ForQuery(QueryFilter query) { CustomFieldQueryBase customFieldQuery = query as CustomFieldQueryBase; if (customFieldQuery == null) return false; return customFieldQuery.definition.Equals(definition); } } internal class CustomFieldStringQueryType : CustomFieldQueryTypeBase { public CustomFieldStringQueryType(int group, ObjectTypes appliesTo, CustomFieldDefinition definition) : base(group, appliesTo, definition) { } public override bool ShowMatchTypeComboButton { get { return true; } } public override bool ShowTextBox(QueryElement queryElement) { return true; } public override object[] MatchTypeComboButtonEntries { get { return StringPropertyQueryType.MatchTypeEntries; } } public override QueryFilter GetQuery(QueryElement queryElement) { StringPropertyQueryType.ExtraComboEntry extraComboEntry = (StringPropertyQueryType.ExtraComboEntry)queryElement.matchTypeComboButton.SelectedItem.Tag; return new CustomFieldQuery(definition, queryElement.textBox.Text, extraComboEntry.type); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { CustomFieldQuery customFieldQuery = query as CustomFieldQuery; queryElement.matchTypeComboButton.SelectItem( delegate(StringPropertyQueryType.ExtraComboEntry entry) { return entry.type == customFieldQuery.type; }); queryElement.textBox.Text = customFieldQuery.query; } } internal class CustomFieldDateQueryType : CustomFieldQueryTypeBase { public CustomFieldDateQueryType(int group, ObjectTypes appliesTo, CustomFieldDefinition definition) : base(group, appliesTo, definition) { } public override bool ShowMatchTypeComboButton { get { return true; } } public override bool ShowDateTimePicker(QueryElement queryElement) { return DatePropertyQueryType.ShouldShowDateTimePicker(queryElement); } public override object[] MatchTypeComboButtonEntries { get { return DatePropertyQueryType.MatchTypeEntries; } } public override QueryFilter GetQuery(QueryElement queryElement) { DatePropertyQueryType.ExtraComboEntry extraComboEntry = (DatePropertyQueryType.ExtraComboEntry)queryElement.matchTypeComboButton.SelectedItem.Tag; return new CustomFieldDateQuery(definition, queryElement.dateTimePicker.Value, extraComboEntry.type); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { CustomFieldDateQuery customFieldQuery = query as CustomFieldDateQuery; queryElement.matchTypeComboButton.SelectItem( delegate(DatePropertyQueryType.ExtraComboEntry entry) { return entry.type == customFieldQuery.type; }); queryElement.dateTimePicker.Value = customFieldQuery.query; } } internal class BooleanQueryType : PropertyQueryType { public BooleanQueryType(int group, ObjectTypes appliesTo, PropertyNames property) : base(group, appliesTo, property) { } public override void FromQuery(QueryFilter query, QueryElement queryElement) { BooleanQuery booleanQuery = query as BooleanQuery; queryElement.ComboButton.SelectItem(delegate(String item) { return (item == Messages.TRUE) == booleanQuery.query; }); } public override bool ShowMatchTypeComboButton { get { return true; } } public override object[] MatchTypeComboButtonEntries { get { return new String[] { Messages.IS }; } } public override bool ShowComboButton(QueryElement queryElement) { return true; } public override Object[] GetComboButtonEntries(QueryElement queryElement) { return new String[] { Messages.TRUE, Messages.FALSE }; } public override QueryFilter GetQuery(QueryElement queryElement) { bool query = queryElement.ComboButton.SelectedItem.Tag as String == Messages.TRUE; return new BooleanQuery(property, query); } } internal class NullQueryType : PropertyQueryType where T : XenObject { bool isNull; public NullQueryType(int group, ObjectTypes appliesTo, PropertyNames property, bool isNull, String i18n) : base(group, appliesTo, property, i18n) { this.isNull = isNull; } public override QueryFilter GetQuery(QueryElement queryElement) { return new NullQuery(property, isNull); } public override bool ForQuery(QueryFilter query) { NullQuery nullQuery = query as NullQuery; return (nullQuery != null && nullQuery.property == property && nullQuery.query == isNull); } public override void FromQuery(QueryFilter query, QueryElement queryElement) { // nothing to do because there are no further choices } } internal abstract class MatchQueryType : QueryType { private String i18n; public MatchQueryType(int group, ObjectTypes appliesTo, String i18n) : base(group, appliesTo) { this.i18n = i18n; } public override String ToString() { return i18n; } protected abstract MatchType[] MatchTypes { get; } public override bool ShowMatchTypeComboButton { get { return true; } } public override object[] MatchTypeComboButtonEntries { get { return MatchTypes; } } public override bool ShowComboButton(QueryElement queryElement) { MatchType matchType = queryElement.matchTypeComboButton.SelectedItem.Tag as MatchType; if (matchType == null) return false; return matchType.ShowComboButton(queryElement); } public override bool ShowNumericUpDown(QueryElement queryElement) { MatchType matchType = queryElement.matchTypeComboButton.SelectedItem.Tag as MatchType; if (matchType == null) return false; return matchType.ShowNumericUpDown(queryElement); } public override Object[] GetComboButtonEntries(QueryElement queryElement) { MatchType matchType = queryElement.matchTypeComboButton.SelectedItem.Tag as MatchType; if (matchType == null) return null; return matchType.ComboButtonEntries; } public override bool ShowTextBox(QueryElement queryElement) { MatchType matchType = queryElement.matchTypeComboButton.SelectedItem.Tag as MatchType; if (matchType == null) return false; return matchType.ShowTextBox(queryElement); } public override string Units(QueryElement queryElement) { MatchType matchType = queryElement.matchTypeComboButton.SelectedItem.Tag as MatchType; if (matchType == null) return String.Empty; return matchType.Units(queryElement); } public override QueryFilter GetQuery(QueryElement queryElement) { MatchType matchType = queryElement.matchTypeComboButton.SelectedItem.Tag as MatchType; if (matchType == null) return null; return matchType.GetQuery(queryElement); } public override bool ForQuery(QueryFilter query) { foreach (MatchType matchType in MatchTypes) if (matchType.ForQuery(query)) return true; return false; } public override void FromQuery(QueryFilter query, QueryElement queryElement) { foreach (MatchType matchType in MatchTypes) if (matchType.ForQuery(query)) { queryElement.matchTypeComboButton.SelectItem(delegate(MatchType entry) { return entry == matchType; }); matchType.FromQuery(query, queryElement); return; } } } public abstract class MatchType { private String matchText; protected MatchType(String matchText) { this.matchText = matchText; } public override string ToString() { return matchText; } public virtual bool ShowComboButton(QueryElement queryElement) { return false; } public virtual bool ShowTextBox(QueryElement queryElement) { return false; } public virtual bool ShowNumericUpDown(QueryElement queryElement) { return false; } public virtual String Units(QueryElement queryElement) { return String.Empty; } public virtual Object[] ComboButtonEntries { get { return null; } } public abstract QueryFilter GetQuery(QueryElement queryElement); public abstract bool ForQuery(QueryFilter query); public abstract void FromQuery(QueryFilter query, QueryElement queryElement); } internal class XMOListContains : MatchType where T : XenObject { private PropertyNames property; private bool contains; private Predicate filter; public XMOListContains(PropertyNames property, bool contains, String matchText, Predicate filter) : base(matchText) { this.property = property; this.contains = contains; this.filter = filter; } public override bool ShowComboButton(QueryElement queryElement) { return true; } public override object[] ComboButtonEntries { get { List entries = new List(); foreach (IXenConnection connection in ConnectionsManager.XenConnectionsCopy) connection.Cache.AddAll(entries, filter); return entries.ToArray(); } } public override QueryFilter GetQuery(QueryElement queryElement) { T t = queryElement.ComboButton.SelectedItem.Tag as T; if (t == null) return null; return new XenModelObjectListContainsQuery(property, t, contains); } public override bool ForQuery(QueryFilter query) { XenModelObjectListContainsQuery xmoQuery = query as XenModelObjectListContainsQuery; if (xmoQuery == null) return false; return xmoQuery.property == property && xmoQuery.contains == contains; } public override void FromQuery(QueryFilter query, QueryElement queryElement) { XenModelObjectListContainsQuery xmoQuery = query as XenModelObjectListContainsQuery; queryElement.ComboButton.SelectItem(delegate(T t) { return Helpers.GetUuid(t) == xmoQuery.queryUUID; }); } } internal class IntMatch : MatchType { private readonly PropertyNames property; private readonly IntPropertyQuery.PropertyQueryType type; private readonly String units; private readonly long multiplier; public IntMatch(PropertyNames property, String matchText, String units, long multiplier, IntPropertyQuery.PropertyQueryType type) : base(matchText) { this.property = property; this.type = type; this.units = units; this.multiplier = multiplier; } public override bool ShowNumericUpDown(QueryElement queryElement) { return true; } public override string Units(QueryElement queryElement) { return units; } public override QueryFilter GetQuery(QueryElement queryElement) { long value = multiplier * (long)queryElement.numericUpDown.Value; return new IntPropertyQuery(property, value, type); } public override bool ForQuery(QueryFilter query) { IntPropertyQuery intQuery = query as IntPropertyQuery; if (intQuery == null) return false; return intQuery.property == property && intQuery.type == type; } public override void FromQuery(QueryFilter query, QueryElement queryElement) { IntPropertyQuery intQuery = query as IntPropertyQuery; if (intQuery == null) return; queryElement.numericUpDown.Value = (decimal) intQuery.query / multiplier; } } // NOT USED FOR NEW QUERIES internal class DiskQueryType : MatchQueryType { private readonly MatchType[] matchType = new MatchType[] { new XMOListContains(PropertyNames.storage, true, Messages.CONTAINED_IN, null), new XMOListContains(PropertyNames.storage, false, Messages.NOT_CONTAINED_IN, null), new XMOListContains(PropertyNames.vm, true, Messages.ATTACHED_TO, VmFilter), new XMOListContains(PropertyNames.vm, false, Messages.NOT_ATTACHED_TO, VmFilter), new IntMatch(PropertyNames.size, Messages.SIZE_IS, Messages.VAL_GIGB, Util.BINARY_GIGA, IntPropertyQuery.PropertyQueryType.exactmatch), new IntMatch(PropertyNames.size, Messages.BIGGER_THAN, Messages.VAL_GIGB, Util.BINARY_GIGA, IntPropertyQuery.PropertyQueryType.gt), new IntMatch(PropertyNames.size, Messages.SMALLER_THAN, Messages.VAL_GIGB, Util.BINARY_GIGA, IntPropertyQuery.PropertyQueryType.lt), }; public DiskQueryType(int group, ObjectTypes appliesTo, String i18n) : base(group, appliesTo, i18n) { } protected override MatchType[] MatchTypes { get { return matchType; } } private static bool VmFilter(VM vm) { return !(vm.is_a_template || vm.is_control_domain) && vm.VBDs.Count > 0; } } internal class LongQueryType : MatchQueryType { private readonly MatchType[] matchType; public LongQueryType(int group, ObjectTypes appliesTo, String i18n, PropertyNames property, long multiplier, String unit) : base(group, appliesTo, i18n) { matchType = new MatchType[] { new IntMatch(property, Messages.BIGGER_THAN, unit, multiplier, IntPropertyQuery.PropertyQueryType.gt), new IntMatch(property, Messages.SMALLER_THAN, unit, multiplier, IntPropertyQuery.PropertyQueryType.lt), new IntMatch(property, Messages.IS_EXACTLY, unit, multiplier, IntPropertyQuery.PropertyQueryType.exactmatch), }; } protected override MatchType[] MatchTypes { get { return matchType; } } } #endregion } }