//****************************************************************************************************** // AdapterCollectionBase.cs - Gbtc // // Copyright © 2010, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php // // 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. // //****************************************************************************************************** using GSF.IO; using GSF.Units; using System; using System.Collections.Concurrent; 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 System.Threading; namespace GSF.TimeSeries.Adapters { /// /// Represents a collection of implementations. /// /// Type of this collection contains. [EditorBrowsable(EditorBrowsableState.Never)] public abstract class AdapterCollectionBase : Collection, IAdapterCollection where T : 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 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 IAdapterCollection m_parent; private Dictionary m_settings; private DataSet m_dataSource; private string m_dataMember; private int m_initializationTimeout; private bool m_autoStart; private bool m_processMeasurementFilter; 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 ConcurrentDictionary m_waitHandles; 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 System.Timers.Timer m_monitorTimer; private bool m_monitorTimerEnabled; private bool m_enabled; private bool m_disposed; #endregion #region [ Constructors ] /// /// Constructs a new instance of the . /// /// Wait handle dictionary. protected AdapterCollectionBase(ConcurrentDictionary waitHandles) { m_name = this.GetType().Name; m_settings = new Dictionary(); m_startTimeConstraint = DateTime.MinValue; m_stopTimeConstraint = DateTime.MaxValue; m_processingInterval = -1; m_initializationTimeout = AdapterBase.DefaultInitializationTimeout; m_waitHandles = waitHandles; m_autoStart = true; m_monitorTimer = new System.Timers.Timer(); m_monitorTimer.Elapsed += m_monitorTimer_Elapsed; // We monitor total number of processed measurements every minute m_monitorTimer.Interval = 60000; m_monitorTimer.AutoReset = true; m_monitorTimer.Enabled = false; } /// /// Releases the unmanaged resources before the object is reclaimed by . /// ~AdapterCollectionBase() { Dispose(false); } #endregion #region [ Properties ] /// /// Gets or sets the name of this . /// public virtual string Name { get { return m_name; } set { m_name = value; } } /// /// 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; // Preparse settings upon connection string assignment if (m_connectionString.IsNullOrWhiteSpace()) m_settings = new Dictionary(); else m_settings = m_connectionString.ParseKeyValuePairs(); } } /// /// Gets a read-only reference to the collection that contains this , if any. /// public ReadOnlyCollection Parent { get { return new ReadOnlyCollection(m_parent); } } /// /// 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 flag that determines if measurements being queued for processing should be tested to see if they are in the . /// public virtual bool ProcessMeasurementFilter { get { return m_processMeasurementFilter; } set { m_processMeasurementFilter = value; // Update this flag for items in this collection lock (this) { foreach (T item in this) { item.ProcessMeasurementFilter = m_processMeasurementFilter; } } } } /// /// 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 (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(T) is IActionAdapter) return this.Cast().Where(item => item.RequestedInputMeasurementKeys != null).SelectMany(item => item.RequestedInputMeasurementKeys).Distinct().ToArray(); if (typeof(T) is IOutputAdapter) 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(T) is IActionAdapter) return this.Cast().Where(item => item.RequestedOutputMeasurementKeys != null).SelectMany(item => item.RequestedOutputMeasurementKeys).Distinct().ToArray(); if (typeof(T) is IInputAdapter) 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 { get { return false; } } /// /// Gets the start time temporal procesing 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 { get { return 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 { get { return 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, overwhich 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 (IAdapter item in this) { item.ProcessingInterval = m_processingInterval; } } } } /// /// 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 (IAdapter 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 a value indicating whether the is read-only. /// public virtual bool IsReadOnly { get { return 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 detemines if implementations are automatically initialized /// when they are added to the collection. /// protected virtual bool AutoInitialize { get { return true; } } /// /// Gets settings parsed when was assigned. /// public Dictionary Settings { get { return m_settings; } } /// /// Gets the descriptive status of this . /// public virtual string Status { get { StringBuilder status = new StringBuilder(); DataSet dataSource = this.DataSource; // Show collection status status.AppendFormat(" Total adapter components: {0}", Count); status.AppendLine(); status.AppendFormat(" Collection initialized: {0}", Initialized); status.AppendLine(); status.AppendFormat(" Parent collection: {0}", m_parent == null ? "Undefined" : m_parent.Name); status.AppendLine(); status.AppendFormat(" Initialization timeout: {0}", InitializationTimeout < 0 ? "Infinite" : InitializationTimeout.ToString() + " milliseconds"); status.AppendLine(); status.AppendFormat(" Using measurement routing: {0}", !ProcessMeasurementFilter); 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}", m_processedMeasurements.ToString("N0")); status.AppendLine(); status.AppendFormat(" Average processing rate: {0} measurements / second", ((int)(m_processedMeasurements / m_totalProcessTime)).ToString("N0")); 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 as IProvideStatus; 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... m_waitHandles = null; } } 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 (DataSource == null) throw new NullReferenceException(string.Format("DataSource is null, cannot load {0}", Name)); if (DataMember.IsNullOrWhiteSpace()) throw new InvalidOperationException(string.Format("DataMember is null or empty, cannot load {0}", 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(string.Format("Data set member \"{0}\" was not found in data source, check ConfigurationEntity. Failed to initialize {1}.", DataMember, Name)); } } /// /// Gets a common wait handle for inter-adapter synchronization. /// /// Case-insensitive wait handle name. /// A based wait handle associated with the given . public virtual AutoResetEvent GetExternalEventHandle(string name) { if (m_waitHandles != null) return m_waitHandles.GetOrAdd(name, key => new AutoResetEvent(false)); return null; } /// /// 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 (adapterRow == null) throw new NullReferenceException(string.Format("Cannot initialize from null adpater 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 (typeName.IsNullOrWhiteSpace()) 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(new InvalidOperationException(string.Format("Failed to load adapter \"{0}\" [{1}] from \"{2}\": {3}", name, typeName, 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) => string.Compare(item.Name, value, true) == 0, 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) { // Stop old item oldAdapter.Stop(); // Dispose old item, initialize new item this[i] = newAdapter; try { // Attempt to start new item if (AutoInitialize) ThreadPool.QueueUserWorkItem(StartItem, newAdapter); else if (AutoStart) newAdapter.Start(); } catch (Exception ex) { // Process exception for logging string errorMessage = string.Format("Failed to queue start operation for adapter {0}: {1}", newAdapter.Name, ex.Message); OnProcessException(new InvalidOperationException(errorMessage, ex)); } foundItem = true; break; } } // Add item to collection if it didn't exist if (!foundItem) { // Add new adapter to the collection Add(newAdapter); try { // Start new item if (AutoInitialize) ThreadPool.QueueUserWorkItem(StartItem, newAdapter); else if (AutoStart) newAdapter.Start(); } catch (Exception ex) { // Process exception for logging string errorMessage = string.Format("Failed to queue start operation for adapter {0}: {1}", newAdapter.Name, ex.Message); OnProcessException(new InvalidOperationException(errorMessage, ex)); } } return true; } } break; } } return false; } /// /// Starts, or restarts, each implementation in this . /// [AdapterCommand("Starts, or restarts, each adapter in the collection.")] public virtual void Start() { // Make sure we are stopped (e.g., disconnected) before attempting to start (e.g., connect) if (m_enabled) Stop(); m_enabled = true; ResetStatistics(); lock (this) { foreach (T item in this) { try { try { // We start items from thread pool if auto-intializing since // start will block and wait for initialization to complete if (AutoInitialize) ThreadPool.QueueUserWorkItem(StartItem, item); else if (AutoStart) item.Start(); } catch (Exception ex) { // Process exception for logging string errorMessage = string.Format("Failed to queue start operation for adapter {0}: {1}", item.Name, ex.Message); OnProcessException(new InvalidOperationException(errorMessage, ex)); } } catch (Exception ex) { // We report any errors encountered during type creation... OnProcessException(new InvalidOperationException(string.Format("Failed to start adapter: {0}", ex.Message), ex)); } } } // Start data monitor... if (MonitorTimerEnabled) m_monitorTimer.Start(); } // Thread pool delegate to handle item startup private void StartItem(object state) { T item = (T)state; try { // Wait for adapter intialization to complete and see if item is set to auto-start if (item.WaitForInitialize(item.InitializationTimeout)) { if (item.AutoStart) item.Start(); } else { OnProcessException(new TimeoutException("Timeout waiting for adapter initialization.")); } } catch (Exception ex) { // We report any errors encountered during startup... OnProcessException(new InvalidOperationException(string.Format("Failed to start adapter: {0}", ex.Message), ex)); } } /// /// Stops each implementation in this . /// [AdapterCommand("Stops each adapter in the collection.")] public virtual void Stop() { m_enabled = false; lock (this) { foreach (T item in this) { item.Stop(); } } // Stop data monitor... m_monitorTimer.Stop(); } /// /// Assigns the reference to the parent that will contain this , if any. /// /// Parent adapter collection. protected virtual void AssignParentCollection(IAdapterCollection parent) { m_parent = parent; } void IAdapter.AssignParentCollection(IAdapterCollection parent) { AssignParentCollection(parent); } /// /// Resets the statistics of this collection. /// [AdapterCommand("Resets the statistics of this collection.")] public void ResetStatistics() { m_processedMeasurements = 0; m_totalProcessTime = 0.0D; m_lastProcessTime = DateTime.UtcNow.Ticks; OnStatusMessage("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 string.Format("Total components: {0:N0}", Count).CenterText(maxLength); } /// /// This method does not wait for . /// /// This parameter is ignored. /// true for . [EditorBrowsable(EditorBrowsableState.Never)] public virtual bool WaitForInitialize(int timeout) { // Adapter collections have no need to wait return true; } /// /// Defines a temporal processing constraint for the adapter collection and applies this contraint 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 contraint 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 multitple 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.")] public virtual void SetTemporalConstraint(string startTime, string stopTime, string constraintParameters) { if (!startTime.IsNullOrWhiteSpace()) m_startTimeConstraint = AdapterBase.ParseTimeTag(startTime); else m_startTimeConstraint = DateTime.MinValue; if (!stopTime.IsNullOrWhiteSpace()) 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 event. /// /// Processing . internal protected virtual void OnProcessException(Exception ex) { if (ProcessException != null) ProcessException(this, new EventArgs(ex)); } /// /// Raises the event. /// /// New status message. protected virtual void OnStatusMessage(string status) { try { if (StatusMessage != null) StatusMessage(this, new EventArgs(status)); } catch (Exception ex) { // We protect our code from consumer thrown exceptions OnProcessException(new InvalidOperationException(string.Format("Exception in consumer handler for StatusMessage event: {0}", ex.Message), ex)); } } /// /// Raises the event with a formatted status message. /// /// Formatted status message. /// Arguments for . /// /// This overload combines string.Format and SendStatusMessage for convienence. /// internal protected virtual void OnStatusMessage(string formattedStatus, params object[] args) { try { if (StatusMessage != null) StatusMessage(this, new EventArgs(string.Format(formattedStatus, args))); } catch (Exception ex) { // We protect our code from consumer thrown exceptions OnProcessException(new InvalidOperationException(string.Format("Exception in consumer handler for StatusMessage event: {0}", ex.Message), ex)); } } /// /// Raises event. /// protected virtual void OnInputMeasurementKeysUpdated() { try { if (InputMeasurementKeysUpdated != null) InputMeasurementKeysUpdated(this, EventArgs.Empty); } catch (Exception ex) { // We protect our code from consumer thrown exceptions OnProcessException(new InvalidOperationException(string.Format("Exception in consumer handler for InputMeasurementKeysUpdated event: {0}", ex.Message), ex)); } } /// /// Raises event. /// protected virtual void OnOutputMeasurementsUpdated() { try { if (OutputMeasurementsUpdated != null) OutputMeasurementsUpdated(this, EventArgs.Empty); } catch (Exception ex) { // We protect our code from consumer thrown exceptions OnProcessException(new InvalidOperationException(string.Format("Exception in consumer handler for OutputMeasurementsUpdated event: {0}", ex.Message), ex)); } } /// /// 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) { if ((object)item != null) { // Wire up events item.StatusMessage += item_StatusMessage; item.ProcessException += item_ProcessException; item.InputMeasurementKeysUpdated += item_InputMeasurementKeysUpdated; item.OutputMeasurementsUpdated += item_OutputMeasurementsUpdated; item.Disposed += item_Disposed; // Associate parent collection item.AssignParentCollection(this); // Update adapter routing type flag item.ProcessMeasurementFilter = ProcessMeasurementFilter; try { // If automatically initializing new elements, handle object initialization from // thread pool so it can take needed amount of time if (AutoInitialize) ThreadPool.QueueUserWorkItem(InitializeItem, item); } catch (Exception ex) { // Process exception for logging string errorMessage = string.Format("Failed to queue initialize operation for adapter {0}: {1}", item.Name, ex.Message); OnProcessException(new InvalidOperationException(errorMessage, ex)); } } } // Thread pool delegate to handle item initialization private void InitializeItem(object state) { T item = (T)state; try { item.Initialize(); item.Initialized = true; } catch (Exception ex) { // We report any errors encountered during initialization... OnProcessException(ex); } } /// /// Unwires events and disposes of implementation. /// /// to dispose. /// /// Derived classes should override if more events are defined. /// protected virtual void DisposeItem(T item) { if ((object)item != null) { // Un-wire events item.StatusMessage -= item_StatusMessage; item.ProcessException -= item_ProcessException; item.InputMeasurementKeysUpdated -= item_InputMeasurementKeysUpdated; item.OutputMeasurementsUpdated -= item_OutputMeasurementsUpdated; // Make sure initialization handles are cleared in case any failed // initializations are still pending item.Initialized = true; // Dissociate parent collection item.AssignParentCollection(null); // Dipose of item, then un-wire disposed event item.Dispose(); item.Disposed -= item_Disposed; } } // Raise status message event on behalf of each item in collection private void item_StatusMessage(object sender, EventArgs e) { if ((object)StatusMessage != null) StatusMessage(sender, e); } // Raise process exception event on behalf of each item in collection private void item_ProcessException(object sender, EventArgs e) { if ((object)ProcessException != null) ProcessException(sender, e); } // Raise input measurement keys updated event on behalf of each item in collection private void item_InputMeasurementKeysUpdated(object sender, EventArgs e) { if ((object)InputMeasurementKeysUpdated != null) InputMeasurementKeysUpdated(sender, e); } // Raise output measurements updated event on behalf of each item in collection private void item_OutputMeasurementsUpdated(object sender, EventArgs e) { if ((object)OutputMeasurementsUpdated != null) OutputMeasurementsUpdated(sender, e); } // Raise disposed event on behalf of each item in collection private void item_Disposed(object sender, EventArgs e) { if ((object)Disposed != null) Disposed(sender, e); } // We monitor the total number of measurements destined for archival here... private void m_monitorTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { StringBuilder status = new StringBuilder(); Ticks currentTime, totalProcessTime; long totalNew, processedMeasurements = this.ProcessedMeasurements; // Calculate time since last call currentTime = DateTime.UtcNow.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().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(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 this.IndexOf((T)item); } } void IList.Insert(int index, IAdapter item) { lock (this) { 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 } }