using System; using System.Collections.Generic; using System.Linq; using System.Xml; using System.Globalization; using System.Reflection; using System.Text.RegularExpressions; using Newtonsoft.Json; using C4IT.Logging; using C4IT.XML; using C4IT.Configuration; using C4IT.FASD.Base; using static C4IT.Logging.cLogManager; namespace C4IT.DataHistoryProvider { public enum eDataHistoryTableCached { Default = 0, Yes = 1, No = 2 }; public enum eDataHistoryQueryType { Query = 0, Static = 1 } public enum eDataHistoryAggregationType { Unknown = 0, first = 1, min = 2, max = 3, sum = 4, average = 5, count = 6, valuecount = 7, latestValue = 8, latestTime = 9 } public class cDataHistoryConfigClusters : cFasdBaseConfig { public static bool confUseBinValuesAsPrimaryKey = true; public const string constFileNameF4sdConfig = "F4SD-DataClusters-Configuration.xml"; private const string constFileNameF4sdSchema = "F4SD-DataClusters-Configuration.xsd"; private const string constConfigRootElement = "F4SD-DataClusters-Configuration"; public static readonly Guid ConfigId = new Guid("28C26B80-C3E9-43E1-934E-57B7C2DBC374"); public Guid getId() { return ConfigId; } public Dictionary Clusters { get; private set; } = null; public cDataHistoryConfigCluster MainCluster { get; private set; } = null; public readonly Dictionary Tables = new Dictionary(); public readonly Dictionary AlternateTableNames = new Dictionary(); internal cDataHistoryConfigClusters() : base(constFileNameF4sdConfig, constFileNameF4sdSchema, constConfigRootElement) { } public override bool InstantiateProperties(XmlElement XRoot, cXmlParser Parser) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { MainCluster = new cDataHistoryConfigClusterMain(XRoot, this, Parser); if (MainCluster == null) { Parser.AddMessage(XRoot, @"No valid main cluster were found in the configuration.", LogLevels.Fatal); return false; } Clusters = cDataHistoryConfigCluster.LoadFromXml(XRoot, this, Parser); if (Clusters == null) { Parser.AddMessage(XRoot, @"No valid clusters were found in the configuration.", LogLevels.Fatal); return false; } Clusters.Add(MainCluster.Name, MainCluster); // resolve the references var _IsValid = true; foreach (var Cluster in Clusters.Values) foreach (var Table in Cluster.Tables.Values) IsValid &= Table.ResolveReferences(Tables, Parser); IsValid = _IsValid; return true; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return false; } public override bool DoXmlUpdates(XmlElement XmlRoot, bool withM42Config = false, bool withIntuneConfig = false, bool withMobileDeviceConfig = false, bool withCitrixConfig = false) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } var RetVal = false; try { RetVal |= DoXmlInsertElement(XmlRoot , "" , "DataCluster[@Name='Main-ComputerInfo']" , "" , "InformationClasses" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='Main-ComputerInfo']" , "Table[@Name='main-computer-quickaction-details']" , "
" , "" ); RetVal |= DoXmlInsertTableRow(XmlRoot, "AD-UserInfo", "ad-user", "", "account"); RetVal |= DoXmlInsertTableRow(XmlRoot, "AD-UserInfo", "ad-user", "", "account"); RetVal |= DoXmlInsertTableRow(XmlRoot, "AD-ComputerInfo", "ad-computer", "", "account"); RetVal |= DoXmlInsertTableRow(XmlRoot, "AD-ComputerInfo", "ad-computer", "", "account"); RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", ""); RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", ""); RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", ""); RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", ""); RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", ""); RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", ""); RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", ""); RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", ""); RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-UserInfo", "agnt-user", ""); RetVal |= DoXmlInsertElement(XmlRoot, "DataCluster[@Name='Agent-DeviceInfo']/Table[@Name='agnt-computer-event-numerical']", "AdditionalTableName[@Name='agnt-computer-event-numerical-latest']", "", "Table-Columns"); RetVal |= DoXmlInsertElement(XmlRoot, "DataCluster[@Name='Agent-DeviceInfo']/Table[@Name='agnt-computer-event-string']", "AdditionalTableName[@Name='agnt-computer-event-string-latest']", "", "Table-Columns"); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='Agent-DeviceInfo']" , "Table[@Name='agnt-computer-event-details-processorTaskManager']" , "
" , "Table[@Name='agnt-computer-event-details-processorUsage']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='Agent-DeviceInfo']" , "Table[@Name='agnt-computer-event-details-processorTaskManager']" , "
" , "Table[@Name='agnt-computer-event-details-processorUsage']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='Agent-DeviceInfo']" , "Table[@Name='agnt-computer-event-details-CPUSpeed']" , "
" , "Table[@Name='agnt-computer-event-details-processorTaskManager']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='Agent-DeviceInfo']" , "Table[@Name='agnt-computer-event-details-ProzessorHighEvents']" , "
" , "Table[@Name='agnt-computer-event-details-CPUSpeed']" ); if (withIntuneConfig) { RetVal |= DoXmlInsertElement(XmlRoot , "" , "DataCluster[@Name='IntuneDeviceInfo']" , "" , "DataCluster[@Name='AD-ComputerInfo']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='IntuneDeviceInfo']" , "Table[@Name='intune-deviceInfo']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "" , "DataCluster[@Name='IntuneUserInfo']" , "" , "DataCluster[@Name='IntuneDeviceInfo']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='IntuneUserInfo']" , "Table[@Name='intune-userDeviceCount']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='IntuneUserInfo']" , "Table[@Name='intune-userDeviceCount-details']" , "
" ); } if (withIntuneConfig && withMobileDeviceConfig) { RetVal |= DoXmlInsertElement(XmlRoot , "" , "DataCluster[@Name='IntuneMobileDeviceInfo']" , "" , "DataCluster[@Name='IntuneUserInfo']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='IntuneMobileDeviceInfo']" , "Table[@Name='intune-mobile-deviceInfo']" , "
" ); } if (withCitrixConfig) { RetVal |= DoXmlInsertElement(XmlRoot , "" , "DataCluster[@Name='citrixMetrics']" , "" , "DataCluster[@Name='AD-ComputerInfo']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-metrics']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-details-icaRttMS']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-details-icaLatency']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-details-clientL7Latency']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-details-serverL7Latency']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-details-inputBandwidthUsed']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-details-outputBandwidthAvailable']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-details-fps']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-details-inputFps']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-details-outputFps']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-details-wanLatency']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-details-dcLatency']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-machine']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-user']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-connection']" , "
" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='citrixMetrics']" , "Table[@Name='citrix-session-currentConnection']" , "
" ); } if (withM42Config) { RetVal |= DoXmlInsertElement(XmlRoot , "" , "DataCluster[@Name='M42Tickets']" , "" , "DataCluster[@Name='AD-ComputerInfo']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42Tickets']" , "Table[@Name='M42Wpm-Tickets']" , "
" , "-first-" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42Tickets']" , "Table[@Name='M42Wpm-Ticket-History']" , "
" , "Table[@Name='M42Wpm-Tickets']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42Tickets']" , "Table[@Name='M42Wpm-Ticket-Services']" , "
" , "Table[@Name='M42Wpm-Ticket-History']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42Tickets']" , "Table[@Name='M42Wpm-Ticket-Assets']" , "
" , "Table[@Name='M42Wpm-Ticket-Services']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42Tickets']" , "Table[@Name='M42Wpm-Ticket-QuickCalls']" , "
" , "Table[@Name='M42Wpm-Ticket-Assets']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42Tickets']" , "Table[@Name='M42Wpm-Ticket-Categories']" , "
" , "Table[@Name='M42Wpm-Ticket-QuickCalls']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42Tickets']" , "Table[@Name='M42Wpm-Ticket-SLAs']" , "
" , "Table[@Name='M42Wpm-Ticket-Categories']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42Tickets']" , "Table[@Name='M42Wpm-Ticket-CloseCase-Services']" , "
" , "Table[@Name='M42Wpm-Ticket-SLAs']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42Tickets']" , "Table[@Name='M42Wpm-Ticket-Roles']" , "
" , "Table[@Name='M42Wpm-Ticket-CloseCase-Services']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42Tickets']" , "Table[@Name='M42Wpm-Ticket-Persons']" , "
" , "Table[@Name='M42Wpm-Ticket-Roles']" ); RetVal |= DoXmlInsertElement(XmlRoot , "" , "DataCluster[@Name='M42UserInfo']" , "" , "DataCluster[@Name='M42Tickets']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42UserInfo']" , "Table[@Name='M42Wpm-User-NewTicket']" , "
" , "-first-" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42UserInfo']" , "Table[@Name='M42Wpm-Pickup-TicketStatus']" , "
" , "Table[@Name='M42Wpm-User-NewTicket']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42UserInfo']" , "Table[@Name='M42Wpm-Pickup-IncidentNotificationMode']" , "
" , "Table[@Name='M42Wpm-Pickup-TicketStatus']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42UserInfo']" , "Table[@Name='M42Wpm-Pickup-TicketTypes']" , "
" , "Table[@Name='M42Wpm-Pickup-IncidentNotificationMode']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42UserInfo']" , "Table[@Name='M42Wpm-Pickup-ActivityImpact']" , "
" , "Table[@Name='M42Wpm-Pickup-TicketTypes']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42UserInfo']" , "Table[@Name='M42Wpm-Pickup-IncidentEntry']" , "
" , "Table[@Name='M42Wpm-Pickup-ActivityImpact']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42UserInfo']" , "Table[@Name='M42Wpm-Pickup-ActivityUrgency']" , "
" , "Table[@Name='M42Wpm-Pickup-IncidentEntry']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42UserInfo']" , "Table[@Name='M42Wpm-Pickup-ActivityErrorType']" , "
" , "Table[@Name='M42Wpm-Pickup-ActivityUrgency']" ); RetVal |= DoXmlInsertElement(XmlRoot , "DataCluster[@Name='M42UserInfo']" , "Table[@Name='M42Wpm-Pickup-ObjectStateReason']" , "
" , "Table[@Name='M42Wpm-Pickup-ActivityErrorType']" ); } // update table 'M42Wpm-Tickets' columns RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Tickets", "", "AffectedUser"); RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Tickets", "", "Urgency"); RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Tickets", "", "UrgencyId"); RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Tickets", "", "Impact"); RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Ticket-QuickCalls", "", "ResponsibleRole"); var quickCallsCategoryColumn = XmlRoot.SelectSingleNode("DataCluster[@Name='M42Tickets']/Table[@Name='M42Wpm-Ticket-QuickCalls']/Table-Columns/Table-Column[@Name='Category']") as XmlElement; if (quickCallsCategoryColumn != null) { if (!string.Equals(quickCallsCategoryColumn.GetAttribute("SourceName"), "Category_Value", StringComparison.OrdinalIgnoreCase)) { quickCallsCategoryColumn.SetAttribute("SourceName", "Category_Value"); RetVal = true; } if (!string.Equals(quickCallsCategoryColumn.GetAttribute("Type"), "guid", StringComparison.OrdinalIgnoreCase)) { quickCallsCategoryColumn.SetAttribute("Type", "guid"); RetVal = true; } } var quickCallsTemplate = XmlRoot.SelectSingleNode("DataCluster[@Name='M42Tickets']/Table[@Name='M42Wpm-Ticket-QuickCalls']/Matrix42-DataQueryItems-Template") as XmlElement; if (quickCallsTemplate != null) { if (!string.Equals(quickCallsTemplate.GetAttribute("DataQueryName"), "C4IT - F4SD - Quick Calls", StringComparison.OrdinalIgnoreCase)) { quickCallsTemplate.SetAttribute("DataQueryName", "C4IT - F4SD - Quick Calls"); RetVal = true; } } var categoriesColumns = XmlRoot.SelectSingleNode("DataCluster[@Name='M42Tickets']/Table[@Name='M42Wpm-Ticket-Categories']/Table-Columns") as XmlElement; if (categoriesColumns != null) { RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Ticket-Categories", "", "Name"); RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Ticket-Categories", "", "parentValue"); var parentValueColumn = categoriesColumns.SelectSingleNode("Table-Column[@Name='parentValue']") as XmlElement; if (parentValueColumn != null) { if (!string.Equals(parentValueColumn.GetAttribute("SourceName"), "Parent_Value", StringComparison.OrdinalIgnoreCase)) { parentValueColumn.SetAttribute("SourceName", "Parent_Value"); RetVal = true; } if (!string.Equals(parentValueColumn.GetAttribute("Type"), "guid", StringComparison.OrdinalIgnoreCase)) { parentValueColumn.SetAttribute("Type", "guid"); RetVal = true; } } var parentColumn = categoriesColumns.SelectSingleNode("Table-Column[@Name='parent']") as XmlElement; if (parentColumn != null) { if (!string.Equals(parentColumn.GetAttribute("SourceName"), "Parent", StringComparison.OrdinalIgnoreCase)) { parentColumn.SetAttribute("SourceName", "Parent"); RetVal = true; } if (!string.Equals(parentColumn.GetAttribute("Type"), "string", StringComparison.OrdinalIgnoreCase)) { parentColumn.SetAttribute("Type", "string"); RetVal = true; } if (!string.Equals(parentColumn.GetAttribute("Cardinal"), "300", StringComparison.OrdinalIgnoreCase)) { parentColumn.SetAttribute("Cardinal", "300"); RetVal = true; } } } // correct the type attribute for the M42Wpm-Ticket-History table to Events var M42JournalItemNode = XmlRoot.SelectSingleNode("DataCluster[@Name='M42Tickets']/Table[@Name='M42Wpm-Ticket-History' and @Type!='HistoryEvents']") as XmlElement; if (M42JournalItemNode != null) { M42JournalItemNode.SetAttribute("Type", "HistoryEvents"); RetVal = true; } } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return RetVal; } public static enumFasdValueType ResultingAggregationType(enumFasdValueType vt, eDataHistoryAggregationType at) { switch (at) { case eDataHistoryAggregationType.count: case eDataHistoryAggregationType.valuecount: return enumFasdValueType.INT; case eDataHistoryAggregationType.latestTime: return enumFasdValueType.DATETIME; case eDataHistoryAggregationType.sum: if (vt == enumFasdValueType.VERSION) return enumFasdValueType.STRING; return vt; } return vt; } public static string GetSqlDataTypeFromHistoryType(enumFasdValueType type, int Cardinal, bool isBinaryType = false) { try { if (isBinaryType) { switch (type) { case enumFasdValueType.VERSION: return "[varbinary](10)"; case enumFasdValueType.MD5: return "[binary](16)"; case enumFasdValueType.SID: return "[varbinary](68)"; case enumFasdValueType.IPV4: return "[binary](4)"; case enumFasdValueType.IPV6: return "[binary](16)"; } return null; } switch (type) { case enumFasdValueType.STRING: if (Cardinal <= 0 || Cardinal >= 4000) return string.Format("[nvarchar](max)", Cardinal); return string.Format("[nvarchar]({0})", Cardinal); case enumFasdValueType.GUID: return "[uniqueidentifier]"; case enumFasdValueType.INT: return "[int]"; case enumFasdValueType.BIGINT: return "[bigint]"; case enumFasdValueType.FLOAT: return "[float]"; case enumFasdValueType.DATETIME: return "[datetime]"; case enumFasdValueType.VERSION: return "[nvarchar](50)"; case enumFasdValueType.MD5: return "[nvarchar](50)"; case enumFasdValueType.SID: return "[nvarchar](200)"; case enumFasdValueType.IPV4: return "[nvarchar](50)"; case enumFasdValueType.IPV6: return "[nvarchar](50)"; case enumFasdValueType.TEXT: return "[ntext]"; case enumFasdValueType.BOOLEAN: return "[tinyint]"; } } catch (Exception E) { LogException(E); } return null; } public static bool CheckColumnCompatibility(cDataHistoryConfigColumnBase C1, cDataHistoryConfigColumnBase C2) { try { var strC1 = C1.SqlType; var strC2 = C2.SqlType; if (confUseBinValuesAsPrimaryKey && C1.SqlTypeBin != null && C2.SqlTypeBin != null) { strC1 = C1.SqlTypeBin; strC2 = C2.SqlTypeBin; } return strC1 == strC2; } catch (Exception E) { LogException(E); } return false; } } public class cDataHistoryConfigCluster : IConfigNodeNamed { public bool IsValid { get; protected set; } = false; public string Name { get; protected set; } = null; public string Description { get; protected set; } = null; [JsonIgnore] public cDataHistoryConfigClusters ParentConfig { get; protected set; } = null; public enumDataHistoryOrigin Origin { get; protected set; } = enumDataHistoryOrigin.Unknown; public enumFasdInformationClass InformationClass { get; protected set; } = enumFasdInformationClass.Unknown; public Dictionary Tables { get; protected set; } = null; internal cDataHistoryConfigCluster() { } internal cDataHistoryConfigCluster(XmlElement XNode, cDataHistoryConfigClusters Config, cXmlParser Parser) { try { if (Config == null) { var E = new Exception("No valid configuration"); LogException(E, LogLevels.Fatal); return; } ParentConfig = Config; Name = cXmlParser.GetStringFromXmlAttribute(XNode, "Name"); if (string.IsNullOrEmpty(Name)) { Parser.AddMessage(XNode, "The element has no valid Name attribute.", LogLevels.Warning); return; } Origin = cXmlParser.GetEnumFromAttribute(XNode, "Origin", enumDataHistoryOrigin.Unknown); if (Origin == enumDataHistoryOrigin.Unknown) { Parser.AddMessage(XNode, $"The element with Name='{Name}' has no valid 'Type' attribute.", LogLevels.Error); return; } InformationClass = cXmlParser.GetEnumFromAttribute(XNode, "InformationClass", enumFasdInformationClass.Unknown); if (InformationClass == enumFasdInformationClass.Unknown) { Parser.AddMessage(XNode, $"The element with Name='{Name}' has no valid 'InformationClass' attribute.", LogLevels.Error); return; } Description = cXmlParser.GetStringFromXmlAttribute(XNode, "Description"); Tables = cDataHistoryConfigTable.LoadFromXml(XNode, this, Parser); if (Tables == null) { Parser.AddMessage(XNode, $"No valid tables found in element with Name='{Name}'.", LogLevels.Warning); return; } IsValid = true; } catch (Exception E) { LogException(E); } } internal static Dictionary LoadFromXml(XmlElement XNode, cDataHistoryConfigClusters Config, cXmlParser Parser) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var XDataClusters = XNode.SelectNodes("DataCluster"); if (XDataClusters == null || XDataClusters.Count <= 0) { Parser.AddMessage(XNode, "The element could not be found in the configuration.", LogLevels.Fatal); return null; } Parser.EnterElement("DataCluster"); var RetVal = new Dictionary(XDataClusters.Count); try { foreach (XmlNode XNode2 in XDataClusters) { if (!(XNode2 is XmlElement XDataCluster)) continue; var DataCluster = new cDataHistoryConfigCluster(XDataCluster, Config, Parser); if ((DataCluster != null) && (DataCluster.IsValid)) { if (!RetVal.ContainsKey(DataCluster.Name)) RetVal.Add(DataCluster.Name, DataCluster); else Parser.AddMessage(XDataCluster, $"There's already an element with Name='{DataCluster.Name}'", LogLevels.Warning); } else { var strN = ""; if (DataCluster != null) strN = $" with Name='{DataCluster.Name}'"; Parser.AddMessage(XDataCluster, $"The element{strN} is not valid.", LogLevels.Error); } Parser.SelectElementNext(); } } catch (Exception E) { LogException(E); } finally { Parser.LeaveElement("DataCluster"); } if (RetVal.Count > 0) return RetVal; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } } public class cDataHistoryConfigClusterMain : cDataHistoryConfigCluster { public readonly Dictionary MainTables = null; internal cDataHistoryConfigClusterMain(XmlElement XRoot, cDataHistoryConfigClusters Config, cXmlParser Parser) { try { if (Config == null) { var E = new Exception("No valid configuration"); LogException(E, LogLevels.Fatal); return; } IsValid = true; Name = "Main"; Origin = enumDataHistoryOrigin.Main; InformationClass = enumFasdInformationClass.Main; ParentConfig = Config; var XNode = XRoot.SelectSingleNode("InformationClasses") as XmlElement; if (XNode == null) { Parser.AddMessage(XNode, $"No valid element found.", LogLevels.Error); return; } Parser.EnterElement("InformationClasses"); try { Tables = cDataHistoryConfigTable.LoadFromXml(XNode, this, Parser); if (Tables == null) { Parser.AddMessage(XNode, $"No valid tables found in element with Name='{Name}'.", LogLevels.Warning); return; } } finally { Parser.LeaveElement("InformationClasses"); } IsValid = true; } catch (Exception E) { LogException(E); } } } public class cDataHistoryConfigTable : IConfigNodeNamed { public bool IsValid { get; protected set; } = false; public string Name { get; protected set; } = null; public string SourceName { get; protected set; } = null; public string Description { get; protected set; } = null; public bool LateDelivery { get; set; } = false; public bool HasLateDelivery { get; private set; } = false; public string AutoCreateHistoryTableName { get; private set; } = null; public bool IsBuildInTable { get; private set; } = false; public eDataHistoryTableCached Cached { get; set; } = eDataHistoryTableCached.Default; public bool IsVirtual { get; set; } = false; [JsonIgnore] public cDataHistoryConfigCluster ParentCluster { get; private set; } = null; public eDataHistoryTableType Type { get; private set; } = eDataHistoryTableType.Unknown; [JsonIgnore] public readonly List KeyColumns = null; public readonly List KeyColumnNames = null; [JsonIgnore] public cDataHistoryConfigColumnBase EventTimeColumn { get; private set; } = null; public string EventTimeColumnName { get; private set; } = null; public readonly Dictionary SubTables = null; public readonly Dictionary Columns = new Dictionary(); public readonly Dictionary Indexes = new Dictionary(); public readonly Dictionary References = new Dictionary(); public readonly bool NoTimeRestriction = false; public List AlternateNames = null; [JsonIgnore] public bool HasScanId = false; internal cDataHistoryConfigTable() { KeyColumns = new List(); KeyColumnNames = new List(); SubTables = new Dictionary(); Columns = new Dictionary(); } internal cDataHistoryConfigTable(XmlElement XNode, cDataHistoryConfigCluster Cluster, cXmlParser Parser) { try { if (Cluster == null) { LogException(new Exception("no valid Cluster information delivered.")); return; } ParentCluster = Cluster; if (Cluster.Origin == enumDataHistoryOrigin.Main && XNode.Name == "InformationClass") IsBuildInTable = true; if (Cluster.Origin == enumDataHistoryOrigin.F4sdAgent || Cluster.Origin == enumDataHistoryOrigin.M42Wpm || Cluster.Origin == enumDataHistoryOrigin.Intune || Cluster.Origin == enumDataHistoryOrigin.Citrix) IsVirtual = true; Name = cXmlParser.GetStringFromXmlAttribute(XNode, "Name"); if (string.IsNullOrEmpty(Name)) { Parser.AddMessage(XNode, $"The element <{XNode.Name}> has no valid 'Name' attribute.", LogLevels.Warning); return; } if (IsBuildInTable) Name = "main-" + Name.ToLowerInvariant(); SourceName = cXmlParser.GetStringFromXmlAttribute(XNode, "SourceName"); if (string.IsNullOrEmpty(SourceName)) SourceName = Name; Cached = cXmlParser.GetEnumFromAttribute(XNode, "Cached", eDataHistoryTableCached.Default); string strKeyColumnNames = cXmlParser.GetStringFromXmlAttribute(XNode, "Key"); if (IsBuildInTable) strKeyColumnNames = "id"; else strKeyColumnNames = cXmlParser.GetStringFromXmlAttribute(XNode, "Key"); if (string.IsNullOrEmpty(strKeyColumnNames)) { Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{Name}' has no valid 'Key' attribute.", LogLevels.Warning); return; } var arrKeyColumnNames = strKeyColumnNames.Split(' '); KeyColumnNames = new List(); foreach (var Entry in arrKeyColumnNames) { var strName = Entry.Trim(); if (!string.IsNullOrEmpty(strName)) KeyColumnNames.Add(strName); } if (KeyColumnNames.Count == 0) { Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{Name}' is not valid because of an empty 'Key' attribute.", LogLevels.Warning); return; } if (IsBuildInTable) Type = eDataHistoryTableType.Static; else Type = cXmlParser.GetEnumFromAttribute(XNode, "Type", eDataHistoryTableType.Unknown); if (Type == eDataHistoryTableType.Unknown) { Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{Name}' has no valid 'Type' attribute.", LogLevels.Warning); return; } EventTimeColumnName = cXmlParser.GetStringFromXmlAttribute(XNode, "EventTimeCol"); Description = cXmlParser.GetStringFromXmlAttribute(XNode, "Description"); LateDelivery = cXmlParser.GetBoolFromXmlAttribute(XNode, "LateDelivery"); var XNode2 = XNode.SelectSingleNode("AutoCreate-Historic-Table"); if (XNode2 != null) { Parser.EnterElement("AutoCreate-Historic-Table"); AutoCreateHistoryTableName = cXmlParser.GetStringFromXmlAttribute(XNode2, "Name"); Parser.LeaveElement("AutoCreate-Historic-Table"); } if (XNode.Name == "JoinedTable") { // we have a table definition with several subtables var XSubTables = XNode.SelectNodes("SubTable"); if ((XSubTables == null) || XSubTables.Count == 0) { Parser.AddMessage(XNode, $"The element <{XNode.Name}> has no valid subtables.", LogLevels.Warning); return; } SubTables = new Dictionary(XSubTables.Count); Parser.EnterElement("SubTable"); try { foreach (XmlNode XEntry in XSubTables) { if (!(XEntry is XmlElement XSubTable)) continue; var SubTable = new cDataHistoryConfigSubTable(XSubTable, this, Parser); if ((SubTable == null) || !SubTable.IsValid || SubTables.ContainsKey(SubTable.Name)) { Parser.AddMessage(XNode, $"The element <{XSubTable.Name}> is not valid.", LogLevels.Warning); } else SubTables.Add(SubTable.Name, SubTable); Parser.SelectElementNext(); } } catch (Exception E) { LogException(E); } finally { Parser.LeaveElement("SubTable"); } } else { // we have no explicit subtable definitions var SubTable = new cDataHistoryConfigSubTable(XNode, this, Parser); if ((SubTable == null) || !SubTable.IsValid) { Parser.AddMessage(XNode, $"The element <{XNode.Name}> is not valid.", LogLevels.Warning); return; } SubTables = new Dictionary(1) { { SubTable.Name, SubTable } }; } if (SubTables.Count == 0) { Parser.AddMessage(XNode, $"The table <{XNode.Name}> has no valid subtables.", LogLevels.Warning); return; } // get the key columns KeyColumns = new List(); foreach (var ColName in KeyColumnNames) { if (!Columns.TryGetValue(ColName, out var KeyColumn)) { Parser.AddMessage(XNode, $"The key column name '{ColName}' within the element <{XNode.Name}> could not be found.", LogLevels.Error); return; } KeyColumns.Add(KeyColumn); } // check if we have the key columns in each subtable with the same type foreach (var SubTable in SubTables.Values) { HasLateDelivery |= SubTable.HasLateDelivery; foreach (var KeyColumn in KeyColumns) { if (!SubTable.Columns.TryGetValue(KeyColumn.Name, out var Col)) { Parser.AddMessage(XNode, $"The key column '{KeyColumn.Name}' within the element <{XNode.Name}> could not be found in subtable '{SubTable.Name}'.", LogLevels.Warning); return; } if (Col.ValueType != KeyColumn.ValueType) { Parser.AddMessage(XNode, $"The value type of the key column '{KeyColumn.Name}' within the element <{XNode.Name}> is not compatible to the subtable '{SubTable.Name}'.", LogLevels.Warning); return; } if (Col.Cardinal != KeyColumn.Cardinal) { Parser.AddMessage(XNode, $"The value cardinal of the key column '{KeyColumn.Name}' within the element <{XNode.Name}> is not compatible to the subtable '{SubTable.Name}'.", LogLevels.Warning); return; } } } // get the event time column for event tables if (!string.IsNullOrEmpty(EventTimeColumnName)) if (Columns.TryGetValue(EventTimeColumnName, out var _ec) && _ec.ValueType == enumFasdValueType.DATETIME) EventTimeColumn = _ec; if (Type == eDataHistoryTableType.Events || Type == eDataHistoryTableType.HistoryEvents) { if (EventTimeColumn == null) { Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{Name}' has no valid 'EventTimeCol' attribute.", LogLevels.Warning); return; } } // get the alternatetable names var XAlternates = XNode.SelectNodes("AdditionalTableName"); if (XAlternates != null && XAlternates.Count > 0) { Parser.EnterElement("AdditionalTableName"); foreach (XmlNode XEntry in XAlternates) { if (!(XEntry is XmlElement XAlternate)) continue; try { var _an = cXmlParser.GetStringFromXmlAttribute(XAlternate, "Name"); if (string.IsNullOrWhiteSpace(_an)) continue; if (AlternateNames == null) AlternateNames = new List(XAlternates.Count); if (!AlternateNames.Contains(_an)) AlternateNames.Add(_an); } catch (Exception E) { LogException(E); } finally { Parser.SelectElementNext(); } } Parser.LeaveElement("AdditionalTableName"); } // get the indexes var XIndexes = XNode.SelectNodes("Table-Index"); if (XIndexes != null && XIndexes.Count > 0) { Parser.EnterElement("Table-Index"); foreach (XmlNode XEntry in XIndexes) { if (!(XEntry is XmlElement XIndex)) continue; try { var Index = new cDataHistoryConfigTableIndex { Name = XIndex.GetAttribute("Name") }; if (string.IsNullOrEmpty(Index.Name)) { Parser.AddMessage(XIndex, $"The element <{XIndex.Name}> has no valid 'Name' attribute.", LogLevels.Warning); continue; } Index.Unique = cXmlParser.GetBoolFromXmlAttribute(XIndex, "Unique"); var strColumns = XIndex.GetAttribute("Columns"); if (string.IsNullOrEmpty(strColumns)) { Parser.AddMessage(XIndex, $"The element <{XIndex.Name}> with Name='{Index.Name}' has no valid 'Columns' attribute.", LogLevels.Warning); continue; } var arrCols = strColumns.Split(' '); foreach (var strCol in arrCols) { var strCol2 = strCol.Trim(); if (strCol2 == "") continue; if (!Columns.TryGetValue(strCol2, out var Col)) { Parser.AddMessage(XIndex, $"The element <{XIndex.Name}> with Name='{Index.Name}' contains a non existing element '{strCol2}' within the 'Columns' attribute.", LogLevels.Warning); Index.Columns.Clear(); break; } if (Index.Columns.Contains(Col)) { Parser.AddMessage(XIndex, $"The element <{XIndex.Name}> with Name='{Index.Name}' contains a duplicated element '{strCol2}' within the 'Columns' attribute.", LogLevels.Warning); Index.Columns.Clear(); break; } Index.Columns.Add(Col); } if (Columns.Count == 0) { Parser.AddMessage(XIndex, $"The element <{XIndex.Name}> with Name='{Index.Name}' has an illegal or no valid entries in the 'Columns' attribute.", LogLevels.Warning); continue; } if (Indexes.ContainsKey(Index.Name)) { Parser.AddMessage(XIndex, $"The element <{XIndex.Name}> with Name='{Index.Name}' is duplicated.", LogLevels.Warning); continue; } Indexes.Add(Index.Name, Index); } catch (Exception E) { LogException(E); } finally { Parser.SelectElementNext(); } } Parser.LeaveElement("Table-Index"); } // get the table references var XJoins = XNode.SelectNodes("Table-Reference"); if (XJoins != null && XJoins.Count > 0) { Parser.EnterElement("Table-Reference"); foreach (XmlNode XEntry in XJoins) { if (!(XEntry is XmlElement XJoin)) continue; try { var Join = new cDataHistoryConfigTableReferenceInfo(XJoin, this, Parser); if (!Join.IsValid) { var strWithName = ""; if (Join.ForeignTableName != null) strWithName = $"with Name='{Join.ForeignTableName}'"; Parser.AddMessage(XJoin, $"The element{strWithName} is not valid.", LogLevels.Warning); continue; } if (References.ContainsKey(Join.ForeignTableName)) { Parser.AddMessage(XJoin, $"The element <{XJoin.Name}> with Table='{Join.ForeignTableName}' within table '{Name}' is duplicated.", LogLevels.Warning); continue; } References.Add(Join.ForeignTableName, Join); } catch (Exception E) { LogException(E); } finally { Parser.SelectElementNext(); } } Parser.LeaveElement("Table-Reference"); } // resolve the columns of the computations bool _IsValid = true; foreach (var Column in Columns.Values) { if (!(Column is cDataHistoryConfigColumnComputation CP)) continue; if (CP.Computation != null) _IsValid &= CP.Computation.Resolve(Columns, Parser); } IsValid = _IsValid; } catch (Exception E) { IsValid = false; LogException(E); } finally { } } internal static Dictionary LoadFromXml(XmlElement XNode, cDataHistoryConfigCluster Cluster, cXmlParser Parser) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var XTableList = new List(3); var XList = XNode.SelectNodes("Table"); if (XList != null && XList.Count > 0) XTableList.Add(XList); XList = XNode.SelectNodes("JoinedTable"); if (XList != null && XList.Count > 0) XTableList.Add(XList); XList = XNode.SelectNodes("InformationClass"); if (XList != null && XList.Count > 0) XTableList.Add(XList); if (XTableList.Count == 0) return null; var RetVal = new Dictionary(); foreach (XmlNodeList XTables in XTableList) { Parser.EnterElement(XTables[0].Name); foreach (XmlNode XNode2 in XTables) { if (!(XNode2 is XmlElement XTable)) continue; var Table = new cDataHistoryConfigTable(XTable, Cluster, Parser); if ((Table != null) && Table.IsValid) { if (!RetVal.ContainsKey(Table.Name)) { if (!Cluster.ParentConfig.Tables.ContainsKey(Table.Name)) { RetVal.Add(Table.Name, Table); if (!Cluster.ParentConfig.Tables.ContainsKey(Table.Name)) Cluster.ParentConfig.Tables.Add(Table.Name, Table); } else { Parser.AddMessage(XTable, $"There's already an <{XTable.Name}> element with Name='{Table.Name}'", LogLevels.Warning); } } else { Parser.AddMessage(XTable, $"There's already an <{XTable.Name}> element with Name='{Table.Name}'", LogLevels.Warning); } if (Table.AlternateNames != null) { foreach (var _an in Table.AlternateNames) Cluster.ParentConfig.AlternateTableNames[_an] = Table.Name; } } else { var strN = ""; if (Table != null) strN = $" with Name='{Table.Name}'"; Parser.AddMessage(XTable, $"The <{XTable.Name}> element{strN} is not valid.", LogLevels.Error); } Parser.SelectElementNext(); } Parser.LeaveElement(XTables[0].Name); } if (RetVal.Count > 0) { var _lstAuto = new List(); foreach (var Entry in RetVal.Values) { if (!string.IsNullOrEmpty(Entry.AutoCreateHistoryTableName)) _lstAuto.Add(Entry); } foreach (var Entry in _lstAuto) { var _tbl = cDataHistoryConfigTable.CreateHistoricTableFromDetails(Entry); if (_tbl != null) { if (!RetVal.ContainsKey(_tbl.Name)) { RetVal.Add(_tbl.Name, _tbl); if (!Cluster.ParentConfig.Tables.ContainsKey(_tbl.Name)) Cluster.ParentConfig.Tables.Add(_tbl.Name, _tbl); } } } return RetVal; } } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } public override string ToString() { return Name; } public bool IsUniqueColumn(cDataHistoryConfigColumnBase Column) { if (KeyColumns.Contains(Column)) return true; if (EventTimeColumn == Column) return true; foreach (var Index in Indexes.Values) { if (!Index.Unique) continue; if (Index.Columns.Contains(Column)) return true; } return false; } public bool IsNullable(cDataHistoryConfigColumnBase Column) { return !IsUniqueColumn(Column); } internal bool ResolveReferences(Dictionary Tables, cXmlParser Parser) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var RetVal = true; var delList = new List(); foreach (var Ref in this.References.Values) { RetVal &= Ref.Resolve(this, Tables, Parser); if (!Ref.IsValid) delList.Add(Ref); } foreach (var Ref in delList) this.References.Remove(Ref.ForeignTableName); return RetVal; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return false; } internal static cDataHistoryConfigTable CreateHistoricTableFromDetails(cDataHistoryConfigTable SourceTable) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var _newTable = new cDataHistoryConfigTable() { IsValid = true, Name = SourceTable.AutoCreateHistoryTableName, SourceName = SourceTable.SourceName, Description = SourceTable.Description, LateDelivery = SourceTable.LateDelivery, IsBuildInTable = SourceTable.IsBuildInTable, Cached = SourceTable.Cached, IsVirtual = SourceTable.IsVirtual, ParentCluster = SourceTable.ParentCluster, Type = eDataHistoryTableType.History }; var _newSubTable = new cDataHistoryConfigSubTable(_newTable) { IsValid = true, Name = SourceTable.AutoCreateHistoryTableName, Description = SourceTable.Description }; _newTable.SubTables.Add(_newSubTable.Name, _newSubTable); foreach (var Entry in SourceTable.KeyColumns) { var _colId = new cDataHistoryConfigColumn(Entry.Name, _newSubTable, Entry.ValueType, Entry.Cardinal) { SourceName = Entry.SourceName, AggregationType = eDataHistoryAggregationType.first }; _newSubTable.Columns.Add(_colId.Name, _colId); _newTable.Columns.Add(_colId.Name, _colId); _newTable.KeyColumns.Add(_colId); _newTable.KeyColumnNames.Add(_colId.Name); } var _colTime = new cDataHistoryConfigColumn(SourceTable.EventTimeColumn.Name, _newSubTable, SourceTable.EventTimeColumn.ValueType, SourceTable.EventTimeColumn.Cardinal) { SourceName = SourceTable.EventTimeColumn.SourceName, AggregationType = eDataHistoryAggregationType.count }; _newSubTable.Columns.Add(_colTime.Name, _colTime); _newTable.Columns.Add(_colTime.Name, _colTime); _newTable.EventTimeColumn = _colTime; _newTable.EventTimeColumnName = _colTime.Name; return _newTable; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } } public class cDataHistoryConfigSubTable : IConfigNodeNamed { public bool IsValid { get; internal set; } = false; public string Name { get; set; } = null; public string Description { get; internal set; } = null; public bool Aggregation { get; private set; } = false; public bool HasLateDelivery { get; private set; } = false; [JsonIgnore] public readonly cDataHistoryConfigTable ParentTable = null; public readonly cDataHistoryConfigQueryTemplate Template = null; public readonly Dictionary Columns = null; internal cDataHistoryConfigSubTable(cDataHistoryConfigTable Table) { ParentTable = Table; Columns = new Dictionary(); } internal cDataHistoryConfigSubTable(XmlElement XNode, cDataHistoryConfigTable Table, cXmlParser Parser) { try { if (Table == null) { LogException(new Exception("No valid Tabel parameter used."), LogLevels.Fatal); return; } ParentTable = Table; Name = cXmlParser.GetStringFromXmlAttribute(XNode, "Name"); if (string.IsNullOrEmpty(Name)) { Parser.AddMessage(XNode, $"The element <{XNode.Name}> has no valid 'Name' attribute", LogLevels.Warning); return; } Description = cXmlParser.GetStringFromXmlAttribute(XNode, "Description"); Template = cDataHistoryConfigQueryTemplate.LoadFromXml(XNode, Parser); // ToDo: Check, if we need a template /* if (Template == null) { Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{Name}' has no valid '