//******************************************************************************************************
// DatabaseNotifier.cs - Gbtc
//
// Copyright © 2018, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
// file except in compliance with the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 08/03/2018 - J. Ritchie Carroll
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using GSF;
using GSF.Data;
using GSF.Diagnostics;
using GSF.Parsing;
using GSF.Threading;
using GSF.TimeSeries;
using GSF.TimeSeries.Adapters;
namespace DynamicCalculator
{
///
/// The DatabaseNotifier is an action adapter which takes multiple input measurements and defines
/// a boolean expression such that when the expression is true a database operation is triggered.
///
[Description("Database Notifier: Executes a database operation based on a custom boolean expression")]
public class DatabaseNotifier : DynamicCalculator
{
#region [ Members ]
// Constants
private const string DefaultExpressionText = "x > 0";
private const string DefaultDatabaseConnectionString = "";
private const string DefaultDatabaseProviderString = "AssemblyName={System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089}; ConnectionType=System.Data.SqlClient.SqlConnection; AdapterType=System.Data.SqlClient.SqlDataAdapter";
private const string DefaultDatabaseCommand = "sp_LogSsamEvent";
private const string DefaultDatabaseCommandParameters = "1,1,'FL_PMU_{Acronym}_HEARTBEAT','','{Acronym} adapter heartbeat at {Timestamp} UTC',''";
private const double DefaultDatabaseMaximumWriteInterval = DelayedSynchronizedOperation.DefaultDelay / 1000.0D;
private const int DefaultFramesPerSecond = 30;
private const double DefaultLagTime = 5.0D;
private const double DefaultLeadTime = 5.0D;
// Fields
private DelayedSynchronizedOperation m_databaseOperation;
private long m_expressionSuccesses;
private long m_expressionFailures;
private long m_totalDatabaseOperations;
private object m_lastDatabaseOperationResult;
#endregion
#region [ Properties ]
///
/// Gets or sets the boolean expression used to determine if the database operation should be executed.
///
[ConnectionStringParameter]
[Description("Defines the boolean expression used to determine if the database operation should be executed.")]
[DefaultValue(DefaultExpressionText)]
public new string ExpressionText // Redeclared to provide a more relevant description and example value for this adapter
{
get => base.ExpressionText;
set => base.ExpressionText = value;
}
///
/// Gets or sets the connection string used for database operation. Leave blank to use local configuration database defined in "systemSettings".
///
[ConnectionStringParameter]
[Description("Defines the connection string used for database operation. Leave blank to use local configuration database defined in \"systemSettings\".")]
[DefaultValue(DefaultDatabaseConnectionString)]
public string DatabaseConnnectionString
{
get;
set;
}
///
/// Gets or sets the provider string used for database operation. Defaults to a SQL Server provider string.
///
[ConnectionStringParameter]
[Description("Defines the provider string used for database operation. Defaults to a SQL Server provider string.")]
[DefaultValue(DefaultDatabaseProviderString)]
public string DatabaseProviderString
{
get;
set;
}
///
/// Gets or sets the command used for database operation, e.g., a stored procedure name or SQL expression like "INSERT".
///
[ConnectionStringParameter]
[Description("Defines the command used for database operation, e.g., a stored procedure name or SQL expression like \"INSERT\".")]
[DefaultValue(DefaultDatabaseCommand)]
public string DatabaseCommand
{
get;
set;
}
///
/// Gets or sets the parameters for the command that includes any desired value substitutions used for database operation. Available substitutions: {Acronym} and {Timestamp}.
///
[ConnectionStringParameter]
[Description("Defines the parameters for the command that includes any desired value substitutions used for database operation. Available substitutions: {Acronym} and {Timestamp}.")]
[DefaultValue(DefaultDatabaseCommandParameters)]
public string DatabaseCommandParameters
{
get;
set;
}
///
/// Gets or sets the maximum interval, in seconds, at which the adapter can execute database operations. Set to zero for no delay.
///
[ConnectionStringParameter]
[Description("Defines the maximum interval, in seconds, at which the adapter can execute database operations. Set to zero for no delay.")]
[DefaultValue(DefaultDatabaseMaximumWriteInterval)]
public double DatabaseMaximumWriteInterval
{
get;
set;
}
///
/// Gets or sets the number of frames per second.
///
///
/// Valid frame rates for a are greater than 0 frames per second.
///
[ConnectionStringParameter]
[Description("Defines the number of frames per second expected by the adapter.")]
[DefaultValue(DefaultFramesPerSecond)]
public new int FramesPerSecond // Redeclared to provide a default value since property is not commonly used
{
get => base.FramesPerSecond;
set => base.FramesPerSecond = value;
}
///
/// Gets or sets the allowed past time deviation tolerance, in seconds (can be sub-second).
///
///
/// Defines the time sensitivity to past measurement timestamps.
/// The number of seconds allowed before assuming a measurement timestamp is too old.
/// This becomes the amount of delay introduced by the concentrator to allow time for data to flow into the system.
///
/// LagTime must be greater than zero, but it can be less than one.
[ConnectionStringParameter]
[Description("Defines the allowed past time deviation tolerance, in seconds (can be sub-second).")]
[DefaultValue(DefaultLagTime)]
public new double LagTime // Redeclared to provide a default value since property is not commonly used
{
get => base.LagTime;
set => base.LagTime = value;
}
///
/// Gets or sets the allowed future time deviation tolerance, in seconds (can be sub-second).
///
///
/// Defines the time sensitivity to future measurement timestamps.
/// The number of seconds allowed before assuming a measurement timestamp is too advanced.
/// This becomes the tolerated +/- accuracy of the local clock to real-time.
///
/// LeadTime must be greater than zero, but it can be less than one.
[ConnectionStringParameter]
[Description("Defines the allowed future time deviation tolerance, in seconds (can be sub-second).")]
[DefaultValue(DefaultLeadTime)]
public new double LeadTime // Redeclared to provide a default value since property is not commonly used
{
get => base.LeadTime;
set => base.LeadTime = value;
}
///
/// Gets or sets output measurements that the action adapter will produce, if any.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override IMeasurement[] OutputMeasurements // Redeclared to hide property - not relevant to this adapter
{
get => base.OutputMeasurements;
set => base.OutputMeasurements = value;
}
///
/// Gets or sets the source of the timestamps of the calculated values.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public new TimestampSource TimestampSource // Redeclared to hide property - not relevant to this adapter
{
get => base.TimestampSource;
set => base.TimestampSource = value;
}
///
/// Gets flag that determines if the implementation of the requires an output measurement.
///
protected override bool ExpectsOutputMeasurement => false;
///
/// Returns the detailed status of the data input source.
///
public override string Status
{
get
{
StringBuilder status = new StringBuilder();
status.Append(base.Status);
status.AppendLine();
status.AppendFormat(" Expression Successes: {0:N0}", m_expressionSuccesses);
status.AppendLine();
status.AppendFormat(" Expression Failures: {0:N0}", m_expressionFailures);
status.AppendLine();
status.AppendFormat(" Total Database Operations: {0:N0}", m_totalDatabaseOperations);
status.AppendLine();
status.AppendFormat(" Last DB Operation Result: {0}", m_lastDatabaseOperationResult?.ToString() ?? "null");
status.AppendLine();
return status.ToString();
}
}
#endregion
#region [ Methods ]
///
/// Initializes .
///
public override void Initialize()
{
Dictionary settings = Settings;
if (!settings.TryGetValue(nameof(ExpressionText), out _))
settings[nameof(ExpressionText)] = DefaultExpressionText;
if (!settings.TryGetValue(nameof(FramesPerSecond), out _))
settings[nameof(FramesPerSecond)] = DefaultFramesPerSecond.ToString();
if (!settings.TryGetValue(nameof(LagTime), out _))
settings[nameof(LagTime)] = DefaultLagTime.ToString();
if (!settings.TryGetValue(nameof(LeadTime), out _))
settings[nameof(LeadTime)] = DefaultLeadTime.ToString();
base.Initialize();
// Load optional database settings
if (settings.TryGetValue(nameof(DatabaseConnnectionString), out string setting) && !string.IsNullOrWhiteSpace(setting))
DatabaseConnnectionString = setting;
else
DatabaseConnnectionString = DefaultDatabaseConnectionString;
if (settings.TryGetValue(nameof(DatabaseProviderString), out setting) && !string.IsNullOrWhiteSpace(setting))
DatabaseProviderString = setting;
else
DatabaseProviderString = DefaultDatabaseProviderString;
if (settings.TryGetValue(nameof(DatabaseCommand), out setting) && !string.IsNullOrWhiteSpace(setting))
DatabaseCommand = setting;
else
DatabaseCommand = DefaultDatabaseCommand;
if (settings.TryGetValue(nameof(DatabaseCommandParameters), out setting) && !string.IsNullOrWhiteSpace(setting))
DatabaseCommandParameters = setting;
else
DatabaseCommandParameters = DefaultDatabaseCommandParameters;
if (settings.TryGetValue(nameof(DatabaseMaximumWriteInterval), out setting) && double.TryParse(setting, out double interval))
DatabaseMaximumWriteInterval = interval;
else
DatabaseMaximumWriteInterval = DefaultDatabaseMaximumWriteInterval;
// Define synchronized monitoring operation
m_databaseOperation = new DelayedSynchronizedOperation(DatabaseOperation, exception => OnProcessException(MessageLevel.Warning, exception))
{
Delay = (int)(DatabaseMaximumWriteInterval * 1000.0D)
};
}
///
/// Handler for the values calculated by the .
///
/// The value calculated by the .
protected override void HandleCalculatedValue(object value)
{
if (value.ToString().ParseBoolean())
{
m_expressionSuccesses++;
m_databaseOperation.RunOnceAsync();
}
else
{
m_expressionFailures++;
}
}
///
/// Queues database operation for execution. Operation will execute immediately if not already running.
///
[AdapterCommand("Queues database operation for execution. Operation will execute immediately if not already running.", "Administrator", "Editor")]
public void QueueOperation() => m_databaseOperation?.RunOnce();
private void DatabaseOperation()
{
using (AdoDataConnection connection = string.IsNullOrWhiteSpace(DatabaseConnnectionString) ? new AdoDataConnection("systemSettings") : new AdoDataConnection(DatabaseConnnectionString, DatabaseProviderString))
{
TemplatedExpressionParser parameterTemplate = new TemplatedExpressionParser
{
TemplatedExpression = DatabaseCommandParameters
};
Dictionary substitutions = new Dictionary
{
["{Acronym}"] = Name,
["{Timestamp}"] = RealTime.ToString(TimeTagBase.DefaultFormat)
};
List