/// <summary> /// Parses output measurements from connection string setting. /// </summary> /// <param name="dataSource">The <see cref="DataSet"/> used to define output measurements.</param> /// <param name="allowSelect">Determines if database access via "SELECT" statement should be allowed for defining output measurements (see remarks about security).</param> /// <param name="value">Value of setting used to define output measurements, typically "outputMeasurements".</param> /// <param name="measurementTable">Measurement table name used to load additional meta-data; this is not used when specifying a FILTER expression.</param> /// <returns>User selected output measurements.</returns> /// <remarks> /// Security warning: allowing SELECT statements, i.e., setting <paramref name="allowSelect"/> to <c>true</c>, should only be allowed in cases where SQL /// injection would not be an issue (e.g., in places where a user can already access the database and would have nothing to gain via an injection attack). /// </remarks> public static IMeasurement[] ParseOutputMeasurements(DataSet dataSource, bool allowSelect, string value, string measurementTable = "ActiveMeasurements") { List<IMeasurement> measurements = new List<IMeasurement>(); Measurement measurement; MeasurementKey key; Guid id; string tableName, expression, sortField; int takeCount; bool dataSourceAvailable = (object)dataSource != null; if (string.IsNullOrWhiteSpace(value)) return measurements.ToArray(); value = value.Trim(); if (dataSourceAvailable && ParseFilterExpression(value, out tableName, out expression, out sortField, out takeCount)) { foreach (DataRow row in dataSource.Tables[tableName].Select(expression, sortField).Take(takeCount)) { id = row["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType<Guid>(); key = MeasurementKey.LookUpOrCreate(id, row["ID"].ToString()); measurement = new Measurement { Metadata = key.Metadata, }; measurements.Add(measurement); } } else if (allowSelect && value.StartsWith("SELECT ", StringComparison.OrdinalIgnoreCase)) { try { // Load settings from the system settings category ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings["systemSettings"]; settings.Add("AllowSelectFilterExpresssions", false, "Determines if database backed SELECT statements should be allowed as filter expressions for defining input and output measurements for adapters."); // Global configuration setting can override ability to use SELECT statements if (settings["AllowSelectFilterExpresssions"].ValueAsBoolean()) { using (AdoDataConnection database = new AdoDataConnection("systemSettings")) { DataTable results = database.Connection.RetrieveData(database.AdapterType, value); foreach (DataRow row in results.Rows) { id = row["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType<Guid>(); key = MeasurementKey.LookUpOrCreate(id, row["ID"].ToString()); measurement = new Measurement { Metadata = key.Metadata, }; measurements.Add(measurement); } } } else { throw new InvalidOperationException("Database backed SELECT statements are disabled in the configuration."); } } catch (Exception ex) { throw new InvalidOperationException($"Could not parse output measurement definition from select statement \"{value}\": {ex.Message}", ex); } } else { string[] elem; double adder, multipler; foreach (string item in value.Split(';')) { if (string.IsNullOrWhiteSpace(item)) continue; elem = item.Trim().Split(','); // Trim all tokens ahead of time for (int i = 0; i < elem.Length; i++) elem[i] = elem[i].Trim(); if (Guid.TryParse(elem[0], out id)) { // The item was parsed as a signal ID so do a straight lookup key = MeasurementKey.LookUpBySignalID(id); if (key == MeasurementKey.Undefined && dataSourceAvailable && dataSource.Tables.Contains(measurementTable)) { DataRow[] filteredRows = dataSource.Tables[measurementTable].Select($"SignalID = '{id}'"); if (filteredRows.Length > 0) MeasurementKey.TryCreateOrUpdate(id, filteredRows[0]["ID"].ToString(), out key); } } else if (!MeasurementKey.TryParse(elem[0], out key)) { if (dataSourceAvailable && dataSource.Tables.Contains(measurementTable)) { DataRow[] filteredRows; // The item could not be parsed as a signal ID, but we do have a data source we can use to find the signal ID filteredRows = dataSource.Tables[measurementTable].Select($"ID = '{elem[0]}'"); if (filteredRows.Length == 0) { // Point tags can have commas so we do not support specification of adders and multipliers in this case - // therefore, we must do our lookup based on item (not elem) and clear elem if the lookup is successful filteredRows = dataSource.Tables[measurementTable].Select($"PointTag = '{item.Trim()}'"); if (filteredRows.Length > 0) elem = new string[0]; } if (filteredRows.Length > 0) key = MeasurementKey.LookUpOrCreate(filteredRows[0]["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType<Guid>(), filteredRows[0]["ID"].ToString()); } // If all else fails, attempt to parse the item as a measurement key if (key == MeasurementKey.Undefined) { if (id == Guid.Empty) throw new InvalidOperationException($"Could not parse output measurement definition \"{item}\" as a filter expression, measurement key, point tag or Guid"); throw new InvalidOperationException($"Measurement (targeted for output) with an ID of \"{item}\" is not defined or is not enabled"); } } if (key == MeasurementKey.Undefined) continue; // Adder and multiplier may be optionally specified if (elem.Length < 2 || !double.TryParse(elem[1], out adder)) adder = 0.0D; if (elem.Length < 3 || !double.TryParse(elem[2], out multipler)) multipler = 1.0D; // Create a new measurement for the provided field level information measurement = new Measurement { Metadata = key.Metadata.ChangeAdderMultiplier(adder, multipler), }; // Attempt to lookup other associated measurement meta-data from default measurement table, if defined try { if (dataSourceAvailable && dataSource.Tables.Contains(measurementTable)) { DataRow[] filteredRows = dataSource.Tables[measurementTable].Select($"ID = '{key}'"); if (filteredRows.Length > 0) { DataRow row = filteredRows[0]; measurement.SetTagName(row["PointTag"].ToNonNullString()); // Manually specified adder and multiplier take precedence, but if none were specified, // then those defined in the meta-data are used instead if (elem.Length < 3) measurement.SetMultiplier(double.Parse(row["Multiplier"].ToString())); if (elem.Length < 2) measurement.SetAdder(double.Parse(row["Adder"].ToString())); } } } catch { // Errors here are not catastrophic, this simply limits the available meta-data measurement.SetTagName(string.Empty); } measurements.Add(measurement); } } return measurements.ToArray(); }