//******************************************************************************************************
// AdapterCollectionBase.cs - Gbtc
//
// Copyright © 2012, 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://www.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:
// ----------------------------------------------------------------------------------------------------
// 09/02/2010 - J. Ritchie Carroll
// Generated original version of source code.
// 12/20/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using GSF.Annotations;
using GSF.Collections;
using GSF.Diagnostics;
using GSF.IO;
using GSF.Threading;
using GSF.Units;
namespace GSF.TimeSeries.Adapters
{
///
/// Represents a collection of implementations.
///
/// Type of this collection contains.
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class AdapterCollectionBase : ListCollection, IAdapterCollection where T : class, IAdapter
{
#region [ Members ]
// Events
///
/// Provides status messages to consumer.
///
///
/// is new status message.
///
public event EventHandler> StatusMessage;
///
/// Event is raised when there is an exception encountered while processing.
///
///
/// is the exception that was thrown.
///
public event EventHandler> ProcessException;
///
/// Event is raised when are updated.
///
public event EventHandler InputMeasurementKeysUpdated;
///
/// Event is raised when are updated.
///
public event EventHandler OutputMeasurementsUpdated;
///
/// Event is raised when adapter is aware of a configuration change.
///
public event EventHandler ConfigurationChanged;
///
/// Event is raised when this is disposed or an in the collection is disposed.
///
public event EventHandler Disposed;
// Fields
private string m_name;
private uint m_id;
private bool m_initialized;
private string m_connectionString;
private Dictionary m_settings;
private DataSet m_dataSource;
private string m_dataMember;
private int m_initializationTimeout;
private bool m_autoStart;
private IMeasurement[] m_outputMeasurements;
private MeasurementKey[] m_inputMeasurementKeys;
private string[] m_inputSourceIDs;
private string[] m_outputSourceIDs;
private MeasurementKey[] m_requestedInputMeasurementKeys;
private MeasurementKey[] m_requestedOutputMeasurementKeys;
private Ticks m_lastProcessTime;
private Time m_totalProcessTime;
private long m_processedMeasurements;
private DateTime m_startTimeConstraint;
private DateTime m_stopTimeConstraint;
private int m_processingInterval;
private SharedTimer m_monitorTimer;
private bool m_monitorTimerEnabled;
private readonly LogicalThreadScheduler m_lifecycleThreadScheduler;
private readonly Dictionary m_lifecycleThreads;
private readonly LogicalThreadLocal m_activeItem;
private bool m_enabled;
private long m_startTime;
private long m_stopTime;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Constructs a new instance of the .
///
protected AdapterCollectionBase()
{
Log = Logger.CreatePublisher(GetType(), MessageClass.Application);
m_name = GetType().Name;
Log.InitialStackMessages = Log.InitialStackMessages.Union("AdapterName", m_name);
m_settings = new Dictionary();
m_startTimeConstraint = DateTime.MinValue;
m_stopTimeConstraint = DateTime.MaxValue;
m_processingInterval = -1;
m_initializationTimeout = AdapterBase.DefaultInitializationTimeout;
m_autoStart = true;
// We monitor total number of processed measurements every minute
m_monitorTimer = Common.TimerScheduler.CreateTimer(60000);
m_monitorTimer.Elapsed += m_monitorTimer_Elapsed;
m_monitorTimer.AutoReset = true;
m_monitorTimer.Enabled = false;
m_lifecycleThreadScheduler = new LogicalThreadScheduler();
m_lifecycleThreads = new Dictionary();
m_activeItem = new LogicalThreadLocal();
// Even on a single processor system we want a few threads such that if an
// adapter is taking a long time to initialize, other adapters can still be
// initializing in the meanwhile
if (m_lifecycleThreadScheduler.MaxThreadCount < 4)
m_lifecycleThreadScheduler.MaxThreadCount = 4;
m_lifecycleThreadScheduler.UnhandledException += (sender, args) => OnProcessException(MessageLevel.Warning, args.Argument);
}
///
/// Releases the unmanaged resources before the object is reclaimed by .
///
~AdapterCollectionBase()
{
Dispose(false);
}
#endregion
#region [ Properties ]
///
/// Log messages generated by an adapter.
///
protected LogPublisher Log { get; }
///
/// Gets or sets the name of this .
///
public virtual string Name
{
get
{
return m_name;
}
set
{
m_name = value;
Log.InitialStackMessages = Log.InitialStackMessages.Union("AdapterName", m_name);
}
}
///
/// Gets or sets numeric ID associated with this .
///
public virtual uint ID
{
get
{
return m_id;
}
set
{
m_id = value;
}
}
///
/// Gets or sets flag indicating if the adapter collection has been initialized successfully.
///
public virtual bool Initialized
{
get
{
return m_initialized;
}
set
{
m_initialized = value;
}
}
///
/// Gets or sets key/value pair connection information specific to this .
///
public virtual string ConnectionString
{
get
{
return m_connectionString;
}
set
{
m_connectionString = value;
// Pre-parse settings upon connection string assignment
if (string.IsNullOrWhiteSpace(m_connectionString))
m_settings = new Dictionary();
else
m_settings = m_connectionString.ParseKeyValuePairs();
}
}
string IAdapter.ConnectionInfo => null;
///
/// Gets or sets based data source used to load each .
/// Updates to this property will cascade to all items in this .
///
///
/// Table name specified in from is expected
/// to have the following table column names:
/// ID, AdapterName, AssemblyName, TypeName, ConnectionString
/// ID column type should be integer based, all other column types are expected to be string based.
///
public virtual DataSet DataSource
{
get
{
return m_dataSource;
}
set
{
m_dataSource = value;
// Update data source for items in this collection
lock (this)
{
foreach (T item in this)
{
item.DataSource = m_dataSource;
}
}
}
}
///
/// Gets or sets specific data member (e.g., table name) in used to this .
///
///
/// Table name specified in from is expected
/// to have the following table column names:
/// ID, AdapterName, AssemblyName, TypeName, ConnectionString
/// ID column type should be integer based, all other column types are expected to be string based.
///
public virtual string DataMember
{
get
{
return m_dataMember;
}
set
{
m_dataMember = value;
}
}
///
/// Gets or sets the default adapter time that represents the maximum time system will wait during for initialization.
///
///
/// Set to to wait indefinitely.
///
public virtual int InitializationTimeout
{
get
{
return m_initializationTimeout;
}
set
{
m_initializationTimeout = value;
}
}
///
/// Gets or sets flag indicating if adapter collection should automatically start items when is false.
///
public virtual bool AutoStart
{
get
{
return m_autoStart;
}
set
{
m_autoStart = value;
}
}
///
/// Gets or sets primary keys of input measurements the expects, if any.
///
public virtual MeasurementKey[] InputMeasurementKeys
{
get
{
// If a specific set of input measurement keys has been assigned, use that set
if (m_inputMeasurementKeys != null)
return m_inputMeasurementKeys;
List cumulativeKeys = new List();
// Otherwise return cumulative results of all child adapters
lock (this)
{
foreach (T adapter in this)
{
if ((object)adapter != null)
{
MeasurementKey[] inputMeasurementKeys = adapter.InputMeasurementKeys;
// If any of the children expects all measurements (i.e., null InputMeasurementKeys)
// then the parent collection must expect all measurements
if ((object)inputMeasurementKeys == null)
return null;
if (inputMeasurementKeys.Length > 0)
cumulativeKeys.AddRange(inputMeasurementKeys);
}
}
}
return cumulativeKeys.Distinct().ToArray();
}
set
{
m_inputMeasurementKeys = value;
OnInputMeasurementKeysUpdated();
}
}
///
/// Gets or sets output measurements that the will produce, if any.
///
public virtual IMeasurement[] OutputMeasurements
{
get
{
// If a specific set of output measurements has been assigned, use that set
if (m_outputMeasurements != null)
return m_outputMeasurements;
// Otherwise return cumulative results of all child adapters
List cumulativeMeasurements = new List();
// Otherwise return cumulative results of all child adapters
lock (this)
{
foreach (T adapter in this)
{
if ((object)adapter != null)
{
IMeasurement[] outputMeasurements = adapter.OutputMeasurements;
if (outputMeasurements != null && outputMeasurements.Length > 0)
cumulativeMeasurements.AddRange(outputMeasurements);
}
}
}
return cumulativeMeasurements.Distinct().ToArray();
}
set
{
m_outputMeasurements = value;
OnOutputMeasurementsUpdated();
}
}
///
/// Gets or sets values used to filter input measurement keys.
///
///
/// The collection classes simply track this value if assigned, no automatic action is taken.
///
public virtual string[] InputSourceIDs
{
get
{
return m_inputSourceIDs;
}
set
{
m_inputSourceIDs = value;
}
}
///
/// Gets or sets values used to filter output measurements.
///
///
/// The collection classes simply track this value if assigned, no automatic action is taken.
///
public virtual string[] OutputSourceIDs
{
get
{
return m_outputSourceIDs;
}
set
{
m_outputSourceIDs = value;
}
}
///
/// Gets or sets input measurement keys that are requested by other adapters based on what adapter says it can provide.
///
public virtual MeasurementKey[] RequestedInputMeasurementKeys
{
get
{
// If a specific set of input measurement keys has been assigned, use that set
if (m_requestedInputMeasurementKeys != null)
return m_requestedInputMeasurementKeys;
// Otherwise return cumulative results of all child adapters
lock (this)
{
if (typeof(IActionAdapter).IsAssignableFrom(typeof(T)))
return this.Cast().Where(item => item.RequestedInputMeasurementKeys != null).SelectMany(item => item.RequestedInputMeasurementKeys).Distinct().ToArray();
if (typeof(IOutputAdapter).IsAssignableFrom(typeof(T)))
return this.Cast().Where(item => item.RequestedInputMeasurementKeys != null).SelectMany(item => item.RequestedInputMeasurementKeys).Distinct().ToArray();
}
return null;
}
set
{
m_requestedInputMeasurementKeys = value;
}
}
///
/// Gets or sets output measurement keys that are requested by other adapters based on what adapter says it can provide.
///
public virtual MeasurementKey[] RequestedOutputMeasurementKeys
{
get
{
// If a specific set of output measurement keys has been assigned, use that set
if (m_requestedOutputMeasurementKeys != null)
return m_requestedOutputMeasurementKeys;
// Otherwise return cumulative results of all child adapters
lock (this)
{
if (typeof(IActionAdapter).IsAssignableFrom(typeof(T)))
return this.Cast().Where(item => item.RequestedOutputMeasurementKeys != null).SelectMany(item => item.RequestedOutputMeasurementKeys).Distinct().ToArray();
if (typeof(IInputAdapter).IsAssignableFrom(typeof(T)))
return this.Cast().Where(item => item.RequestedOutputMeasurementKeys != null).SelectMany(item => item.RequestedOutputMeasurementKeys).Distinct().ToArray();
}
return null;
}
set
{
m_requestedOutputMeasurementKeys = value;
}
}
///
/// Gets the flag indicating if this adapter collection supports temporal processing.
///
///
/// For collections this defaults to false.
///
public virtual bool SupportsTemporalProcessing => false;
///
/// Gets the start time temporal processing constraint defined by call to .
///
///
/// This value will be when start time constraint is not set - meaning the adapter
/// is processing data in real-time.
///
public virtual DateTime StartTimeConstraint => m_startTimeConstraint;
///
/// Gets the stop time temporal processing constraint defined by call to .
///
///
/// This value will be when stop time constraint is not set - meaning the adapter
/// is processing data in real-time.
///
public virtual DateTime StopTimeConstraint => m_stopTimeConstraint;
///
/// Gets or sets the desired processing interval, in milliseconds, for the adapter collection and applies this interval to each adapter.
///
///
/// With the exception of the values of -1 and 0, this value specifies the desired processing interval for data, i.e.,
/// basically a delay, or timer interval, over which to process data. A value of -1 means to use the default processing
/// interval while a value of 0 means to process data as fast as possible.
///
public virtual int ProcessingInterval
{
get
{
return m_processingInterval;
}
set
{
m_processingInterval = value;
if (m_processingInterval < -1)
m_processingInterval = -1;
// Apply this new processing interval for all adapters in the collection
lock (this)
{
foreach (T item in this)
{
item.ProcessingInterval = m_processingInterval;
}
}
}
}
///
/// Gets the total amount of time, in seconds, that the adapter has been active.
///
public virtual Time RunTime
{
get
{
Ticks processingTime = 0;
if (m_startTime > 0)
{
if (m_stopTime > 0)
processingTime = m_stopTime - m_startTime;
else
processingTime = DateTime.UtcNow.Ticks - m_startTime;
}
if (processingTime < 0)
processingTime = 0;
return processingTime.ToSeconds();
}
}
///
/// Gets the total number of measurements processed thus far by each implementation
/// in the .
///
public virtual long ProcessedMeasurements
{
get
{
long processedMeasurements = 0;
// Calculate new total for all adapters
lock (this)
{
foreach (T item in this)
{
processedMeasurements += item.ProcessedMeasurements;
}
}
return processedMeasurements;
}
}
///
/// Gets or sets enabled state of this .
///
public virtual bool Enabled
{
get
{
return m_enabled;
}
set
{
if (m_enabled && !value)
Stop();
else if (!m_enabled && value)
Start();
}
}
///
/// Gets the UTC time this was started.
///
public Ticks StartTime => m_startTime;
///
/// Gets the UTC time this was stopped.
///
public Ticks StopTime => m_stopTime;
///
/// Gets a flag that indicates whether the object has been disposed.
///
public bool IsDisposed => m_disposed;
///
/// Gets a value indicating whether the is read-only.
///
public virtual bool IsReadOnly => false;
///
/// Gets or sets flag that determines if monitor timer should be used for monitoring processed measurement statistics for the .
///
protected virtual bool MonitorTimerEnabled
{
get
{
return m_monitorTimerEnabled;
}
set
{
m_monitorTimerEnabled = value;
if (m_monitorTimer != null)
m_monitorTimer.Enabled = value && Enabled;
}
}
///
/// Gets flag that determines if implementations are automatically initialized
/// when they are added to the collection.
///
protected virtual bool AutoInitialize => true;
///
/// Gets settings parsed when was assigned.
///
public Dictionary Settings => m_settings;
///
/// Gets the descriptive status of this .
///
public virtual string Status
{
get
{
StringBuilder status = new StringBuilder();
DataSet dataSource = DataSource;
// Show collection status
status.AppendFormat(" Total adapter components: {0}", Count);
status.AppendLine();
status.AppendFormat(" Collection initialized: {0}", Initialized);
status.AppendLine();
status.AppendFormat(" Initialization timeout: {0}", InitializationTimeout < 0 ? "Infinite" : InitializationTimeout.ToString() + " milliseconds");
status.AppendLine();
status.AppendFormat(" Current operational state: {0}", Enabled ? "Enabled" : "Disabled");
status.AppendLine();
status.AppendFormat(" Temporal processing: {0}", SupportsTemporalProcessing ? "Supported" : "Unsupported");
status.AppendLine();
if (SupportsTemporalProcessing)
{
status.AppendFormat(" Start time constraint: {0}", StartTimeConstraint == DateTime.MinValue ? "Unspecified" : StartTimeConstraint.ToString("yyyy-MM-dd HH:mm:ss.fff"));
status.AppendLine();
status.AppendFormat(" Stop time constraint: {0}", StopTimeConstraint == DateTime.MaxValue ? "Unspecified" : StopTimeConstraint.ToString("yyyy-MM-dd HH:mm:ss.fff"));
status.AppendLine();
status.AppendFormat(" Processing interval: {0}", ProcessingInterval < 0 ? "Default" : (ProcessingInterval == 0 ? "As fast as possible" : ProcessingInterval + " milliseconds"));
status.AppendLine();
}
if (MonitorTimerEnabled)
{
status.AppendFormat(" Processed measurements: {0:N0}", m_processedMeasurements);
status.AppendLine();
status.AppendFormat(" Average processing rate: {0:N0} measurements / second", ((int)(m_processedMeasurements / m_totalProcessTime)));
status.AppendLine();
}
status.AppendFormat(" Data source defined: {0}", dataSource != null);
status.AppendLine();
if (dataSource != null)
{
status.AppendFormat(" Referenced data source: {0}, {1} tables", dataSource.DataSetName, dataSource.Tables.Count);
status.AppendLine();
}
status.AppendFormat(" Data source table name: {0}", DataMember);
status.AppendLine();
Dictionary keyValuePairs = Settings;
char[] keyChars;
string value;
status.AppendFormat(" Connection string: {0} key/value pairs", keyValuePairs.Count);
// 1 2 3 4 5 6 7
// 123456789012345678901234567890123456789012345678901234567890123456789012345678
// Key = Value
// 1 2 3 4 5
// 12345678901234567890123456789012345678901234567890
status.AppendLine();
status.AppendLine();
foreach (KeyValuePair item in keyValuePairs)
{
keyChars = item.Key.Trim().ToCharArray();
keyChars[0] = char.ToUpper(keyChars[0]);
value = item.Value.Trim();
if (value.Length > 50)
value = value.TruncateRight(47) + "...";
status.AppendFormat("{0} = {1}", new string(keyChars).TruncateRight(25).PadLeft(25), value.PadRight(50));
status.AppendLine();
}
status.AppendLine();
if (Count > 0)
{
int index = 0;
status.AppendLine();
status.AppendFormat("Status of each {0} component:", Name);
status.AppendLine();
status.Append(new string('-', 79));
status.AppendLine();
// Show the status of registered components.
lock (this)
{
foreach (T item in this)
{
IProvideStatus statusProvider = item;
if (statusProvider != null)
{
// This component provides status information.
status.AppendLine();
status.AppendFormat("Status of {0} component {1}, {2}:", typeof(T).Name, ++index, statusProvider.Name);
status.AppendLine();
try
{
status.Append(statusProvider.Status);
}
catch (Exception ex)
{
status.AppendFormat("Failed to retrieve status due to exception: {0}", ex.Message);
status.AppendLine();
}
}
}
}
status.AppendLine();
status.Append(new string('-', 79));
status.AppendLine();
}
return status.ToString();
}
}
#endregion
#region [ Methods ]
///
/// Releases all the resources used by the object.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
if (disposing)
{
if (m_monitorTimer != null)
{
m_monitorTimer.Elapsed -= m_monitorTimer_Elapsed;
m_monitorTimer.Dispose();
}
m_monitorTimer = null;
Clear(); // This disposes all items in collection...
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
if (Disposed != null)
Disposed(this, EventArgs.Empty);
}
}
}
///
/// Loads all implementations defined in .
///
///
///
/// Table name specified in from is expected
/// to have the following table column names:
/// ID, AdapterName, AssemblyName, TypeName, ConnectionString
/// ID column type should be integer based, all other column types are expected to be string based.
///
///
/// Note that when calling this method any existing items will be cleared allowing a "re-initialize".
///
///
/// DataSource is null.
/// DataMember is null or empty.
public virtual void Initialize()
{
if ((object)DataSource == null)
throw new NullReferenceException($"DataSource is null, cannot load {Name}");
if (string.IsNullOrWhiteSpace(DataMember))
throw new InvalidOperationException($"DataMember is null or empty, cannot load {Name}");
Initialized = false;
Dictionary settings = Settings;
string setting;
T item;
// Load the default initialization parameter for adapters in this collection
if (settings.TryGetValue("initializationTimeout", out setting))
InitializationTimeout = int.Parse(setting);
lock (this)
{
Clear();
if (DataSource.Tables.Contains(DataMember))
{
foreach (DataRow adapterRow in DataSource.Tables[DataMember].Rows)
{
if (TryCreateAdapter(adapterRow, out item))
Add(item);
}
Initialized = true;
}
else
throw new InvalidOperationException($"Data set member \"{DataMember}\" was not found in data source, check ConfigurationEntity. Failed to initialize {Name}.");
}
}
///
/// Attempts to create an from the specified .
///
/// containing item information to initialize.
/// Initialized adapter if successful; otherwise null.
/// true if item was successfully initialized; otherwise false.
///
/// See property for expected column names.
///
/// is null.
public virtual bool TryCreateAdapter(DataRow adapterRow, out T adapter)
{
if ((object)adapterRow == null)
throw new NullReferenceException("Cannot initialize from null adapter DataRow");
Assembly assembly;
string name = "", assemblyName = "", typeName = "", connectionString, setting;
uint id;
try
{
name = adapterRow["AdapterName"].ToNonNullString("[IAdapter]");
assemblyName = FilePath.GetAbsolutePath(adapterRow["AssemblyName"].ToNonNullString());
typeName = adapterRow["TypeName"].ToNonNullString();
connectionString = adapterRow["ConnectionString"].ToNonNullString();
id = uint.Parse(adapterRow["ID"].ToNonNullString("0"));
if (string.IsNullOrWhiteSpace(typeName))
throw new InvalidOperationException("No adapter type was defined");
if (!File.Exists(assemblyName))
throw new InvalidOperationException("Specified adapter assembly does not exist");
assembly = Assembly.LoadFrom(assemblyName);
adapter = (T)Activator.CreateInstance(assembly.GetType(typeName));
// Assign critical adapter properties
adapter.Name = name;
adapter.ID = id;
adapter.ConnectionString = connectionString;
adapter.DataSource = DataSource;
// Assign adapter initialization timeout
if (adapter.Settings.TryGetValue("initializationTimeout", out setting))
adapter.InitializationTimeout = int.Parse(setting);
else
adapter.InitializationTimeout = InitializationTimeout;
return true;
}
catch (Exception ex)
{
// We report any errors encountered during type creation...
OnProcessException(MessageLevel.Warning, new InvalidOperationException($"Failed to load adapter \"{name}\" [{typeName}] from \"{assemblyName}\": {ex.Message}", ex));
}
adapter = default(T);
return false;
}
// Explicit IAdapter implementation of TryCreateAdapter
bool IAdapterCollection.TryCreateAdapter(DataRow adapterRow, out IAdapter adapter)
{
T adapterT;
bool result = TryCreateAdapter(adapterRow, out adapterT);
adapter = adapterT as IAdapter;
return result;
}
///
/// Attempts to get the adapter with the specified .
///
/// ID of adapter to get.
/// Adapter reference if found; otherwise null.
/// true if adapter with the specified was found; otherwise false.
public virtual bool TryGetAdapterByID(uint id, out T adapter)
{
return TryGetAdapter(id, (item, value) => item.ID == value, out adapter);
}
///
/// Attempts to get the adapter with the specified .
///
/// Name of adapter to get.
/// Adapter reference if found; otherwise null.
/// true if adapter with the specified was found; otherwise false.
public virtual bool TryGetAdapterByName(string name, out T adapter)
{
return TryGetAdapter(name, (item, value) => item.Name.Equals(value, StringComparison.OrdinalIgnoreCase), out adapter);
}
///
/// Attempts to get the adapter with the specified given function.
///
/// Value of adapter to get.
/// Function delegate used to test item .
/// Adapter reference if found; otherwise null.
/// true if adapter with the specified was found; otherwise false.
protected virtual bool TryGetAdapter(TValue value, Func testItem, out T adapter)
{
lock (this)
{
foreach (T item in this)
{
if (testItem(item, value))
{
adapter = item;
return true;
}
}
}
adapter = default(T);
return false;
}
// Explicit IAdapter implementation of TryGetAdapterByID
bool IAdapterCollection.TryGetAdapterByID(uint id, out IAdapter adapter)
{
T adapterT;
bool result = TryGetAdapterByID(id, out adapterT);
adapter = adapterT as IAdapter;
return result;
}
// Explicit IAdapter implementation of TryGetAdapterByName
bool IAdapterCollection.TryGetAdapterByName(string name, out IAdapter adapter)
{
T adapterT;
bool result = TryGetAdapterByName(name, out adapterT);
adapter = adapterT as IAdapter;
return result;
}
///
/// Attempts to initialize (or reinitialize) an individual based on its ID.
///
/// The numeric ID associated with the to be initialized.
/// true if item was successfully initialized; otherwise false.
public virtual bool TryInitializeAdapterByID(uint id)
{
T newAdapter, oldAdapter;
uint rowID;
foreach (DataRow adapterRow in DataSource.Tables[DataMember].Rows)
{
rowID = uint.Parse(adapterRow["ID"].ToNonNullString("0"));
if (rowID == id)
{
if (TryCreateAdapter(adapterRow, out newAdapter))
{
// Found and created new item - update collection reference
bool foundItem = false;
lock (this)
{
for (int i = 0; i < Count; i++)
{
oldAdapter = this[i];
if (oldAdapter.ID == id)
{
// Dispose old item, initialize new item
this[i] = newAdapter;
foundItem = true;
break;
}
}
// Add item to collection if it didn't exist
if (!foundItem)
Add(newAdapter);
return true;
}
}
break;
}
}
return false;
}
///
/// Starts, or restarts, each implementation in this .
///
[AdapterCommand("Starts, or restarts, each adapter in the collection.", "Administrator", "Editor")]
public virtual void Start()
{
LogicalThread lifecycleThread;
// Make sure we are stopped (e.g., disconnected) before attempting to start (e.g., connect)
if (!m_enabled)
{
m_enabled = true;
m_stopTime = 0;
m_startTime = DateTime.UtcNow.Ticks;
ResetStatistics();
lock (this)
{
foreach (T item in this)
{
if (item.Initialized && item.AutoStart && !item.Enabled)
{
// Create local reference to the foreach
// variable to be accessed in the lambda function
T itemRef = item;
// Push start command to the lifecycle thread for the adapter
lifecycleThread = m_lifecycleThreads.GetOrAdd(item.ID, id => m_lifecycleThreadScheduler.CreateThread());
lifecycleThread.Push(() => Start(itemRef));
}
}
}
// Start data monitor...
if (MonitorTimerEnabled)
m_monitorTimer.Start();
}
}
///
/// Stops each implementation in this .
///
[AdapterCommand("Stops each adapter in the collection.", "Administrator", "Editor")]
public virtual void Stop()
{
LogicalThread lifecycleThread;
if (m_enabled)
{
m_enabled = false;
m_stopTime = DateTime.UtcNow.Ticks;
lock (this)
{
foreach (T item in this)
{
if (item.Initialized && item.Enabled)
{
// Create local reference to the foreach
// variable to be accessed in the lambda function
T itemRef = item;
// Push stop command to the lifecycle thread for the adapter
lifecycleThread = m_lifecycleThreads.GetOrAdd(item.ID, id => m_lifecycleThreadScheduler.CreateThread());
lifecycleThread.Push(() => Stop(itemRef));
}
}
}
// Stop data monitor...
m_monitorTimer.Stop();
}
}
///
/// Resets the statistics of this collection.
///
[AdapterCommand("Resets the statistics of this collection.", "Administrator", "Editor")]
public void ResetStatistics()
{
m_processedMeasurements = 0;
m_totalProcessTime = 0.0D;
m_lastProcessTime = DateTime.UtcNow.Ticks;
OnStatusMessage(MessageLevel.Info, "Statistics reset for this collection.");
}
///
/// Gets a short one-line status of this .
///
/// Maximum number of available characters for display.
/// A short one-line summary of the current status of this .
public virtual string GetShortStatus(int maxLength)
{
return $"Total components: {Count:N0}".CenterText(maxLength);
}
///
/// Defines a temporal processing constraint for the adapter collection and applies this constraint to each adapter.
///
/// Defines a relative or exact start time for the temporal constraint.
/// Defines a relative or exact stop time for the temporal constraint.
/// Defines any temporal parameters related to the constraint.
///
///
/// This method defines a temporal processing constraint for an adapter, i.e., the start and stop time over which an
/// adapter will process data. Actual implementation of the constraint will be adapter specific. Implementations
/// should be able to dynamically handle multiple calls to this function with new constraints. Passing in null
/// for the and should cancel the temporal constraint and
/// return the adapter to standard / real-time operation.
///
///
/// The and parameters can be specified in one of the
/// following formats:
///
///
/// Time Format
/// Format Description
///
/// -
/// 12-30-2000 23:59:59.033
/// Absolute date and time.
///
/// -
/// *
/// Evaluates to .
///
/// -
/// *-20s
/// Evaluates to 20 seconds before .
///
/// -
/// *-10m
/// Evaluates to 10 minutes before .
///
/// -
/// *-1h
/// Evaluates to 1 hour before .
///
/// -
/// *-1d
/// Evaluates to 1 day before .
///
///
///
///
[AdapterCommand("Defines a temporal processing constraint for each adapter in the collection.", "Administrator", "Editor", "Viewer")]
public virtual void SetTemporalConstraint(string startTime, string stopTime, string constraintParameters)
{
if (!string.IsNullOrWhiteSpace(startTime))
m_startTimeConstraint = AdapterBase.ParseTimeTag(startTime);
else
m_startTimeConstraint = DateTime.MinValue;
if (!string.IsNullOrWhiteSpace(stopTime))
m_stopTimeConstraint = AdapterBase.ParseTimeTag(stopTime);
else
m_stopTimeConstraint = DateTime.MaxValue;
// Apply temporal constraint to all adapters in this collection
lock (this)
{
foreach (T adapter in this)
{
adapter.SetTemporalConstraint(startTime, stopTime, constraintParameters);
}
}
}
///
/// Raises the event.
///
/// New status message.
[Obsolete("Switch to using overload with MessageLevel parameter - this method may be removed from future builds.", false)]
protected void OnStatusMessage(string status)
{
OnStatusMessage(MessageLevel.Info, status, "Unclassified Status");
}
///
/// Raises the event with a formatted status message.
///
/// Formatted status message.
/// Arguments for .
///
/// This overload combines string.Format and SendStatusMessage for convenience.
///
[StringFormatMethod("formattedStatus"), Obsolete("Switch to using overload with MessageLevel parameter - this method may be removed from future builds.", false)]
protected void OnStatusMessage(string formattedStatus, params object[] args)
{
OnStatusMessage(MessageLevel.Info, string.Format(formattedStatus, args), "Unclassified Status");
}
///
/// Raises the event and sends this data to the .
///
/// The to assign to this message
/// New status message.
/// A fixed string to classify this event; defaults to null.
/// to use, if any; defaults to .
///
/// should be a constant string value associated with what type of message is being
/// generated. In general, there should only be a few dozen distinct event names per class. Exceeding this
/// threshold will cause the EventName to be replaced with a general warning that a usage issue has occurred.
///
protected internal virtual void OnStatusMessage(MessageLevel level, string status, string eventName = null, MessageFlags flags = MessageFlags.None)
{
try
{
Log.Publish(level, flags, eventName, status);
using (Logger.SuppressLogMessages())
StatusMessage?.Invoke(this, new EventArgs(AdapterBase.GetStatusWithMessageLevelPrefix(status, level)));
}
catch (Exception ex)
{
// We protect our code from consumer thrown exceptions
OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for StatusMessage event: {ex.Message}", ex), "ConsumerEventException");
}
}
///
/// Raises the event.
///
/// Processing .
[Obsolete("Switch to using overload with MessageLevel parameter - this method may be removed from future builds.", false)]
protected void OnProcessException(Exception ex)
{
OnProcessException(MessageLevel.Info, ex, "Unclassified Exception");
}
///
/// Raises the event.
///
/// The to assign to this message
/// Processing .
/// A fixed string to classify this event; defaults to null.
/// to use, if any; defaults to .
///
/// should be a constant string value associated with what type of message is being
/// generated. In general, there should only be a few dozen distinct event names per class. Exceeding this
/// threshold will cause the EventName to be replaced with a general warning that a usage issue has occurred.
///
protected internal virtual void OnProcessException(MessageLevel level, Exception exception, string eventName = null, MessageFlags flags = MessageFlags.None)
{
try
{
Log.Publish(level, flags, eventName, exception?.Message, null, exception);
using (Logger.SuppressLogMessages())
ProcessException?.Invoke(this, new EventArgs(exception));
}
catch (Exception ex)
{
// We protect our code from consumer thrown exceptions
Log.Publish(MessageLevel.Info, "ConsumerEventException", $"Exception in consumer handler for ProcessException event: {ex.Message}", null, ex);
}
}
///
/// Raises event.
///
protected virtual void OnInputMeasurementKeysUpdated()
{
try
{
InputMeasurementKeysUpdated?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
// We protect our code from consumer thrown exceptions
OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for InputMeasurementKeysUpdated event: {ex.Message}", ex), "ConsumerEventException");
}
}
///
/// Raises event.
///
protected virtual void OnOutputMeasurementsUpdated()
{
try
{
OutputMeasurementsUpdated?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
// We protect our code from consumer thrown exceptions
OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for OutputMeasurementsUpdated event: {ex.Message}", ex), "ConsumerEventException");
}
}
///
/// Raises event.
///
protected virtual void OnConfigurationChanged()
{
try
{
ConfigurationChanged?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
// We protect our code from consumer thrown exceptions
OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for ConfigurationChanged event: {ex.Message}", ex), "ConsumerEventException");
}
}
///
/// Removes all elements from the .
///
protected override void ClearItems()
{
// Dispose each item before clearing the collection
lock (this)
{
foreach (T item in this)
DisposeItem(item);
base.ClearItems();
}
}
///
/// Inserts an element into the the specified index.
///
/// The zero-based index at which item should be inserted.
/// The implementation to insert.
protected override void InsertItem(int index, T item)
{
lock (this)
{
// Wire up item events and handle item initialization
InitializeItem(item);
base.InsertItem(index, item);
}
}
///
/// Assigns a new element to the at the specified index.
///
/// The zero-based index for which item should be assigned.
/// The implementation to assign.
protected override void SetItem(int index, T item)
{
lock (this)
{
// Dispose of existing item
DisposeItem(this[index]);
// Wire up item events and handle initialization of new item
InitializeItem(item);
base.SetItem(index, item);
}
}
///
/// Removes the element at the specified index of the .
///
/// The zero-based index of the element to remove.
protected override void RemoveItem(int index)
{
// Dispose of item before removing it from the collection
lock (this)
{
DisposeItem(this[index]);
base.RemoveItem(index);
}
}
///
/// Wires events and initializes new implementation.
///
/// New implementation.
///
/// Derived classes should override if more events are defined.
///
protected virtual void InitializeItem(T item)
{
LogicalThread lifecycleThread;
if ((object)item != null)
{
// Wire up events
item.StatusMessage += item_StatusMessage;
item.ProcessException += item_ProcessException;
item.ConfigurationChanged += item_ConfigurationChanged;
item.Disposed += item_Disposed;
try
{
// If automatically initializing new elements, handle object initialization on
// its own thread so it can take needed amount of time
if (AutoInitialize)
{
lifecycleThread = GetLifecycleThread(item);
lifecycleThread.Push(() =>
{
m_activeItem.Value = item;
LogicalThread.CurrentThread.Push(() => Initialize(item));
});
}
}
catch (Exception ex)
{
// Process exception for logging
string errorMessage = $"Failed to queue initialize operation for adapter {item.Name}: {ex.Message}";
OnProcessException(MessageLevel.Warning, new InvalidOperationException(errorMessage, ex));
}
}
}
///
/// Un-wires events and disposes of implementation.
///
/// to dispose.
///
/// Derived classes should override if more events are defined.
///
protected virtual void DisposeItem(T item)
{
LogicalThread lifecycleThread;
if ((object)item != null)
{
lifecycleThread = GetLifecycleThread(item);
lifecycleThread.Push(() => Dispose(item));
}
}
// Handle item initialization
private void Initialize(T item)
{
Action initializationTimeoutAction = null;
ICancellationToken initializationTimeoutToken = null;
if (m_activeItem.Value != item)
return;
try
{
// Initialize item if not initialized already
if (!item.Initialized)
{
// If initialization timeout is specified for this item, start the initialization timeout timer
if (item.InitializationTimeout > 0)
{
initializationTimeoutAction = () =>
{
const string MessageFormat = "Initialization of adapter {0} has exceeded" +
" its timeout of {1} seconds. The adapter may still initialize, however this" +
" may indicate a problem with the adapter. If you consider this to be normal," +
" try adjusting the initialization timeout to suppress this message during" +
" normal operations.";
OnStatusMessage(MessageLevel.Warning, string.Format(MessageFormat, item.Name, item.InitializationTimeout / 1000.0), "Initialization");
// ReSharper disable once AccessToModifiedClosure
initializationTimeoutToken = initializationTimeoutAction?.DelayAndExecute(item.InitializationTimeout);
};
initializationTimeoutToken = initializationTimeoutAction.DelayAndExecute(item.InitializationTimeout);
}
// Initialize the item
item.Initialize();
// Initialization successfully completed, so stop the timeout timer
initializationTimeoutToken?.Cancel();
}
// If the item is set to auto-start and not already started, start it now
if (item.AutoStart && !item.Enabled)
{
LogicalThread.CurrentThread.Push(() =>
{
Start(item);
// Set item to its final initialized state so that
// start and stop commands may be issued to the adapter
item.Initialized = true;
// Now that the adapter is fully initialized,
// attach to these events to react to updates at runtime
item.InputMeasurementKeysUpdated += item_InputMeasurementKeysUpdated;
item.OutputMeasurementsUpdated += item_OutputMeasurementsUpdated;
// If input measurement keys were not updated during initialize of the adapter,
// make sure to notify routing tables that adapter is ready for broadcast
OnInputMeasurementKeysUpdated();
});
}
else
{
// Set item to its final initialized state so that
// start and stop commands may be issued to the adapter
item.Initialized = true;
// Now that the adapter is fully initialized,
// attach to these events to react to updates at runtime
item.InputMeasurementKeysUpdated += item_InputMeasurementKeysUpdated;
item.OutputMeasurementsUpdated += item_OutputMeasurementsUpdated;
// If input measurement keys were not updated during initialize of the adapter,
// make sure to notify routing tables that adapter is ready for broadcast
OnInputMeasurementKeysUpdated();
}
}
catch (Exception ex)
{
// We report any errors encountered during initialization...
OnProcessException(MessageLevel.Warning, new InvalidOperationException($"Failed to initialize adapter {item.Name}: {ex.Message}", ex), "Initialization");
// Initialization failed, so stop the timeout timer
initializationTimeoutToken?.Cancel();
}
}
// Handle item startup
private void Start(T item)
{
if (m_activeItem.Value != item)
return;
try
{
item.Start();
}
catch (Exception ex)
{
// We report any errors encountered during startup...
OnProcessException(MessageLevel.Warning, new InvalidOperationException($"Failed to start adapter {item.Name}: {ex.Message}", ex), "Startup");
}
}
// Handle item stop
private void Stop(T item)
{
if (m_activeItem.Value != item)
return;
if (item.Initialized && item.Enabled)
item.Stop();
}
// Handles item disposal
private void Dispose(T item)
{
// Stop item, then un-wire events
item.Stop();
item.StatusMessage -= item_StatusMessage;
item.ProcessException -= item_ProcessException;
item.InputMeasurementKeysUpdated -= item_InputMeasurementKeysUpdated;
item.OutputMeasurementsUpdated -= item_OutputMeasurementsUpdated;
item.ConfigurationChanged -= item_ConfigurationChanged;
// Dispose of item, then un-wire disposed event
item.Dispose();
item.Disposed -= item_Disposed;
}
// Gets the lifecycle thread for the given item
private LogicalThread GetLifecycleThread(T item)
{
return m_lifecycleThreads.GetOrAdd(item.ID, id =>
{
LogicalThread thread = m_lifecycleThreadScheduler.CreateThread();
thread.UnhandledException += (sender, args) => item_ProcessException(item, args);
return thread;
});
}
// Raise status message event on behalf of each item in collection
private void item_StatusMessage(object sender, EventArgs e) => StatusMessage?.Invoke(sender, e);
// Raise process exception event on behalf of each item in collection
private void item_ProcessException(object sender, EventArgs e) => ProcessException?.Invoke(sender, e);
// Raise input measurement keys updated event on behalf of each item in collection
private void item_InputMeasurementKeysUpdated(object sender, EventArgs e) => InputMeasurementKeysUpdated?.Invoke(sender, e);
// Raise output measurements updated event on behalf of each item in collection
private void item_OutputMeasurementsUpdated(object sender, EventArgs e) => OutputMeasurementsUpdated?.Invoke(sender, e);
// Raise configuration changed event on behalf of each item in collection
private void item_ConfigurationChanged(object sender, EventArgs e) => ConfigurationChanged?.Invoke(sender, e);
// Raise disposed event on behalf of each item in collection
private void item_Disposed(object sender, EventArgs e) => Disposed?.Invoke(sender, e);
// We monitor the total number of measurements destined for archival here...
private void m_monitorTimer_Elapsed(object sender, EventArgs e)
{
StringBuilder status = new StringBuilder();
long totalNew, processedMeasurements = ProcessedMeasurements;
// Calculate time since last call
Ticks currentTime = DateTime.UtcNow.Ticks;
Ticks totalProcessTime = currentTime - m_lastProcessTime;
m_totalProcessTime += totalProcessTime.ToSeconds();
m_lastProcessTime = currentTime;
// Calculate how many new measurements have been received in the last minute...
totalNew = processedMeasurements - m_processedMeasurements;
m_processedMeasurements = processedMeasurements;
// Process statistics for 12 hours total runtime:
//
// 1 1 1
// 12345678901234 12345678901234567 1234567890
// Time span Measurements Per second
// -------------- ----------------- ----------
// Entire runtime 9,999,999,999,999 99,999,999
// Last minute 4,985 83
status.AppendFormat("\r\nProcess statistics for {0} total runtime:\r\n\r\n", m_totalProcessTime.ToString(3).ToLower());
status.Append("Time span".PadRight(14));
status.Append(' ');
status.Append("Measurements".CenterText(17));
status.Append(' ');
status.Append("Per second".CenterText(10));
status.AppendLine();
status.Append(new string('-', 14));
status.Append(' ');
status.Append(new string('-', 17));
status.Append(' ');
status.Append(new string('-', 10));
status.AppendLine();
status.Append("Entire runtime".PadRight(14));
status.Append(' ');
status.Append(m_processedMeasurements.ToString("N0").CenterText(17));
status.Append(' ');
status.Append(((int)(m_processedMeasurements / m_totalProcessTime)).ToString("N0").CenterText(10));
status.AppendLine();
status.Append("Last minute".PadRight(14));
status.Append(' ');
status.Append(totalNew.ToString("N0").CenterText(17));
status.Append(' ');
status.Append(((int)(totalNew / totalProcessTime.ToSeconds())).ToString("N0").CenterText(10));
// Report updated statistics every minute...
OnStatusMessage(MessageLevel.Info, "AdapterCollectionBase", status.ToString());
}
#region [ Explicit IList Implementation ]
void ICollection.Add(IAdapter item)
{
lock (this)
{
Add((T)item);
}
}
bool ICollection.Contains(IAdapter item)
{
lock (this)
{
return Contains((T)item);
}
}
void ICollection.CopyTo(IAdapter[] array, int arrayIndex)
{
lock (this)
{
CopyTo(array.Cast().ToArray(), arrayIndex);
}
}
bool ICollection.Remove(IAdapter item)
{
lock (this)
{
return Remove((T)item);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
IAdapter[] adapters;
lock (this)
{
adapters = new IAdapter[Count];
for (int i = 0; i < Count; i++)
adapters[i] = this[i];
}
foreach (IAdapter item in adapters)
{
yield return item;
}
}
int IList.IndexOf(IAdapter item)
{
lock (this)
{
return IndexOf((T)item);
}
}
void IList.Insert(int index, IAdapter item)
{
lock (this)
{
Insert(index, (T)item);
}
}
IAdapter IList.this[int index]
{
get
{
lock (this)
{
return this[index];
}
}
set
{
lock (this)
{
this[index] = (T)value;
}
}
}
#endregion
#endregion
}
}