//******************************************************************************************************
// AdapterBase.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.Units;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
namespace GSF.TimeSeries.Adapters
{
///
/// Represents the base class for any adapter.
///
public abstract class AdapterBase : IAdapter
{
#region [ Members ]
// Constants
private const int DefaultMeasurementReportingInterval = 100000;
///
/// Default initialization timeout.
///
public const int DefaultInitializationTimeout = 15000;
// 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 is disposed.
///
///
/// If an adapter references another adapter by enumerating the collection, this
/// event should be monitored to release the reference.
///
public event EventHandler Disposed;
// Fields
private string m_name;
private uint m_id;
private string m_connectionString;
private IAdapterCollection m_parent;
private Dictionary m_settings;
private DataSet m_dataSource;
private int m_initializationTimeout;
private bool m_autoStart;
private bool m_processMeasurementFilter;
private ManualResetEvent m_initializeWaitHandle;
private AutoResetEvent[] m_externalEventHandles;
private int m_externalEventTimeout;
private MeasurementKey[] m_inputMeasurementKeys;
private IMeasurement[] m_outputMeasurements;
private List m_inputMeasurementKeysHash;
private long m_processedMeasurements;
private int m_measurementReportingInterval;
private bool m_enabled;
private long m_startTime;
private long m_stopTime;
private DateTime m_startTimeConstraint;
private DateTime m_stopTimeConstraint;
private int m_processingInterval;
private int m_hashCode;
private bool m_initialized;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Constructs a new instance of the .
///
protected AdapterBase()
{
m_name = this.GetType().Name;
m_settings = new Dictionary();
m_startTimeConstraint = DateTime.MinValue;
m_stopTimeConstraint = DateTime.MaxValue;
m_processingInterval = -1;
GenHashCode();
// Create wait handle to use for adapter initialization
m_initializeWaitHandle = new ManualResetEvent(false);
m_initializationTimeout = DefaultInitializationTimeout;
}
///
/// Releases the unmanaged resources before the object is reclaimed by .
///
~AdapterBase()
{
Dispose(false);
}
#endregion
#region [ Properties ]
///
/// Gets or sets the name of this .
///
///
/// Derived classes should provide a name for the adapter.
///
public virtual string Name
{
get
{
return m_name;
}
set
{
m_name = value;
GenHashCode();
}
}
///
/// Gets or sets numeric ID associated with this .
///
public virtual uint ID
{
get
{
return m_id;
}
set
{
m_id = value;
GenHashCode();
}
}
///
/// Gets or sets key/value pair connection information specific to this .
///
///
///
/// Example connection string using manually defined measurements:
/// inputMeasurementKeys={P1:1245;P1:1247;P2:1335};
/// outputMeasurements={P3:1345,60.0,1.0;P3:1346;P3:1347}
/// When defined manually, elements in key:
/// * inputMeasurementKeys are defined as "ArchiveSource:PointID"
/// * outputMeasurements are defined as "ArchiveSource:PointID,Adder,Multiplier", the adder and multiplier are optional
/// defaulting to 0.0 and 1.0 respectively.
///
///
///
/// Example connection string using measurements defined in a table:
/// inputMeasurementKeys={FILTER ActiveMeasurements WHERE Company='GSF' AND SignalType='FREQ' ORDER BY ID};
/// outputMeasurements={FILTER ActiveMeasurements WHERE SignalType IN ('IPHA','VPHA') AND Phase='+'}
///
/// Basic filtering syntax is as follows:
///
/// {FILTER <TableName> WHERE <Expression> [ORDER BY <SortField>]}
///
/// Source tables are expected to have at least the following fields:
///
///
/// Name
/// Type
/// Description.
///
/// -
/// ID
/// NVARCHAR
/// Measurement key formatted as: ArchiveSource:PointID.
///
/// -
/// PointTag
/// NVARCHAR
/// Point tag of measurement.
///
/// -
/// Adder
/// FLOAT
/// Adder to apply to value, if any (default to 0.0).
///
/// -
/// Multiplier
/// FLOAT
/// Multipler to apply to value, if any (default to 1.0).
///
///
///
///
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 .
///
public ReadOnlyCollection Parent
{
get
{
return new ReadOnlyCollection(m_parent);
}
}
///
/// Gets or sets based data source available to this .
///
public virtual DataSet DataSource
{
get
{
return m_dataSource;
}
set
{
m_dataSource = value;
}
}
///
/// Gets or sets 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 should automatically start or otherwise connect on demand.
///
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;
}
}
///
/// Gets or sets primary keys of input measurements the expects, if any.
///
///
/// If your adapter needs to receive all measurements, you must explicitly set InputMeasurementKeys to null.
///
[ConnectionStringParameter,
DefaultValue(null),
Description("Defines primary keys of input measurements the adapter expects; can be one of a filter expression, measurement key, point tag or Guid.")]
public virtual MeasurementKey[] InputMeasurementKeys
{
get
{
return m_inputMeasurementKeys;
}
set
{
m_inputMeasurementKeys = value;
// Update input key lookup hash table
if (value != null && value.Length > 0)
{
m_inputMeasurementKeysHash = new List(value);
m_inputMeasurementKeysHash.Sort();
}
else
m_inputMeasurementKeysHash = null;
OnInputMeasurementKeysUpdated();
}
}
///
/// Gets or sets output measurements that the will produce, if any.
///
[ConnectionStringParameter,
DefaultValue(null),
Description("Defines primary keys of output measurements the adapter expects; can be one of a filter expression, measurement key, point tag or Guid.")]
public virtual IMeasurement[] OutputMeasurements
{
get
{
return m_outputMeasurements;
}
set
{
m_outputMeasurements = value;
OnOutputMeasurementsUpdated();
}
}
///
/// Gets the total number of measurements handled thus far by the .
///
public virtual long ProcessedMeasurements
{
get
{
return m_processedMeasurements;
}
}
///
/// Gets or sets the measurement reporting interval.
///
///
/// This is used to determined how many measurements should be processed before reporting status.
///
public virtual int MeasurementReportingInterval
{
get
{
return m_measurementReportingInterval;
}
set
{
m_measurementReportingInterval = value;
}
}
///
/// Gets or sets flag indicating if the adapter has been initialized successfully.
///
public virtual bool Initialized
{
get
{
return m_initialized;
}
set
{
m_initialized = value;
// When initialization is complete we send notification
if ((object)m_initializeWaitHandle != null)
{
if (value)
m_initializeWaitHandle.Set();
else
m_initializeWaitHandle.Reset();
}
}
}
///
/// Gets or sets enabled state of this .
///
///
/// Base class simply starts or stops based on flag value. Derived classes should
/// extend this if needed to enable or disable operational state of the adapter based on flag value.
///
public 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
{
get
{
return m_startTime;
}
}
///
/// Gets the UTC time this was stopped.
///
public Ticks StopTime
{
get
{
return m_stopTime;
}
}
///
/// Gets the flag indicating if this adapter supports temporal processing.
///
public abstract bool SupportsTemporalProcessing
{
get;
}
///
/// 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.
///
///
/// 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;
}
}
///
/// 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
{
#if UseHighResolutionTime
processingTime = PrecisionTimer.UtcNow.Ticks - m_startTime;
#else
processingTime = DateTime.UtcNow.Ticks - m_startTime;
#endif
}
}
if (processingTime < 0)
processingTime = 0;
return processingTime.ToSeconds();
}
}
///
/// Gets settings parsed when was assigned.
///
public Dictionary Settings
{
get
{
return m_settings;
}
}
///
/// Gets the status of this .
///
///
/// Derived classes should provide current status information about the adapter for display purposes.
///
public virtual string Status
{
get
{
const int MaxMeasurementsToShow = 10;
StringBuilder status = new StringBuilder();
DataSet dataSource = this.DataSource;
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(" Initialization timeout: {0}", InitializationTimeout < 0 ? "Infinite" : InitializationTimeout.ToString() + " milliseconds");
status.AppendLine();
status.AppendFormat(" Using measurement routing: {0}", !ProcessMeasurementFilter);
status.AppendLine();
status.AppendFormat(" Adapter initialized: {0}", Initialized);
status.AppendLine();
status.AppendFormat(" Parent collection: {0}", m_parent == null ? "Undefined" : m_parent.Name);
status.AppendLine();
status.AppendFormat(" Operational state: {0}", Enabled ? "Running" : "Stopped");
status.AppendLine();
status.AppendFormat(" Connect on demand: {0}", !AutoStart);
status.AppendLine();
status.AppendFormat(" Processed measurements: {0}", ProcessedMeasurements);
status.AppendLine();
status.AppendFormat(" Total adapter run time: {0}", RunTime.ToString());
status.AppendLine();
status.AppendFormat(" External event handles: {0}", m_externalEventHandles == null ? "None defined" : m_externalEventHandles.Length + " defined");
status.AppendLine();
if (m_externalEventHandles != null)
{
status.AppendFormat(" External event timeout: {0} milliseconds", m_externalEventTimeout);
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();
}
status.AppendFormat(" Item reporting interval: {0}", MeasurementReportingInterval);
status.AppendLine();
status.AppendFormat(" Adpater ID: {0}", ID);
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 (OutputMeasurements != null && OutputMeasurements.Length > OutputMeasurements.Count(m => m.Key == MeasurementKey.Undefined))
{
status.AppendFormat(" Output measurements: {0} defined measurements", OutputMeasurements.Length);
status.AppendLine();
status.AppendLine();
for (int i = 0; i < Common.Min(OutputMeasurements.Length, MaxMeasurementsToShow); i++)
{
status.Append(OutputMeasurements[i].ToString().TruncateRight(40).PadLeft(40));
status.Append(" ");
status.AppendLine(OutputMeasurements[i].ID.ToString());
}
if (OutputMeasurements.Length > MaxMeasurementsToShow)
status.AppendLine("...".PadLeft(26));
status.AppendLine();
}
if (InputMeasurementKeys != null && InputMeasurementKeys.Length > InputMeasurementKeys.Count(k => k == MeasurementKey.Undefined))
{
status.AppendFormat(" Input measurements: {0} defined measurements", InputMeasurementKeys.Length);
status.AppendLine();
status.AppendLine();
for (int i = 0; i < Common.Min(InputMeasurementKeys.Length, MaxMeasurementsToShow); i++)
{
status.AppendLine(InputMeasurementKeys[i].ToString().TruncateRight(25).CenterText(50));
}
if (InputMeasurementKeys.Length > MaxMeasurementsToShow)
status.AppendLine("...".CenterText(50));
status.AppendLine();
}
return status.ToString();
}
}
///
/// Gets or sets collection of wait handles used to synchronize activity with external events.
///
///
/// Each defined external event in the collection should be acquired via .
///
protected AutoResetEvent[] ExternalEventHandles
{
get
{
return m_externalEventHandles;
}
set
{
if (value != null && value.Length > 0)
m_externalEventHandles = value;
else
m_externalEventHandles = null;
}
}
///
/// Gets or sets maximum to time to wait, in milliseconds, for external events before proceeding.
///
protected int ExternalEventTimeout
{
get
{
return m_externalEventTimeout;
}
set
{
m_externalEventTimeout = value;
}
}
#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 ((object)m_initializeWaitHandle != null)
m_initializeWaitHandle.Close();
m_initializeWaitHandle = null;
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
if (Disposed != null)
Disposed(this, EventArgs.Empty);
}
}
}
///
/// Intializes .
///
public virtual void Initialize()
{
Initialized = false;
Dictionary settings = Settings;
string setting;
if (settings.TryGetValue("inputMeasurementKeys", out setting))
InputMeasurementKeys = AdapterBase.ParseInputMeasurementKeys(DataSource, setting);
else
InputMeasurementKeys = new MeasurementKey[0];
if (settings.TryGetValue("outputMeasurements", out setting))
OutputMeasurements = AdapterBase.ParseOutputMeasurements(DataSource, setting);
if (settings.TryGetValue("measurementReportingInterval", out setting))
MeasurementReportingInterval = int.Parse(setting);
else
MeasurementReportingInterval = DefaultMeasurementReportingInterval;
if (settings.TryGetValue("connectOnDemand", out setting))
AutoStart = !setting.ParseBoolean();
else
AutoStart = true;
string startTime, stopTime, parameters;
bool startTimeDefined = settings.TryGetValue("startTimeConstraint", out startTime);
bool stopTimeDefined = settings.TryGetValue("stopTimeConstraint", out stopTime);
if (startTimeDefined || stopTimeDefined)
{
settings.TryGetValue("timeConstraintParameters", out parameters);
SetTemporalConstraint(startTime, stopTime, parameters);
}
int processingInterval;
if (settings.TryGetValue("processingInterval", out setting) && !setting.IsNullOrWhiteSpace() && int.TryParse(setting, out processingInterval))
ProcessingInterval = processingInterval;
// // Establish any defined external event wait handles needed for inter-adapter synchronization
// if (settings.TryGetValue("waitHandleNames", out setting) && !setting.IsNullOrWhiteSpace())
// m_externalEventHandles = setting.Split(',').Select(GetExternalEventHandle).ToArray();
int waitHandleTimeout;
if (settings.TryGetValue("waitHandleTimeout", out setting) && !setting.IsNullOrWhiteSpace() && int.TryParse(setting, out waitHandleTimeout))
m_externalEventTimeout = waitHandleTimeout;
else
m_externalEventTimeout = 33;
}
///
/// Starts the or restarts it if it is already running.
///
[AdapterCommand("Starts the adapter or restarts it if it is already running.")]
public virtual void Start()
{
// Make sure we are stopped (e.g., disconnected) before attempting to start (e.g., connect)
if (m_enabled)
Stop();
// Wait for adapter intialization to complete...
m_enabled = WaitForInitialize(InitializationTimeout);
if (m_enabled)
{
m_stopTime = 0;
m_startTime = PrecisionTimer.UtcNow.Ticks;
}
else
OnProcessException(new TimeoutException("Failed to start adapter due to timeout waiting for initialization."));
}
///
/// Stops the .
///
[AdapterCommand("Stops the adapter.")]
public virtual void Stop()
{
m_enabled = false;
m_stopTime = PrecisionTimer.UtcNow.Ticks;
}
///
/// Assigns the reference to the parent that will contain this .
///
/// Parent adapter collection.
protected virtual void AssignParentCollection(IAdapterCollection parent)
{
m_parent = parent;
}
void IAdapter.AssignParentCollection(IAdapterCollection parent)
{
AssignParentCollection(parent);
}
///
/// 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)
{
return m_parent.GetExternalEventHandle(name);
}
protected bool WaitForExternalEvents()
{
return WaitForExternalEvents(0);
}
///
/// Waits for all external events to fire; no wait will happen if no external events are defined.
///
/// Maximum to time to wait, in milliseconds, for external events before proceeding - value defaults to .
/// true when every all have received a signal or there are no external events defined; otherwise, false.
protected virtual bool WaitForExternalEvents(int timeout)
{
if (m_externalEventHandles != null)
{
if (timeout == 0)
timeout = m_externalEventTimeout;
return WaitHandle.WaitAll(m_externalEventHandles, timeout);
}
return true;
}
///
/// Manually sets the intialized state of the .
///
/// Desired initialized state.
[AdapterCommand("Manually sets the intialized state of the adapter.")]
public virtual void SetInitializedState(bool initialized)
{
this.Initialized = initialized;
}
///
/// 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 abstract string GetShortStatus(int maxLength);
///
/// Determines if specified measurement key is defined in .
///
/// Primary key of measurement to find.
/// true if specified measurement key is defined in .
public virtual bool IsInputMeasurement(MeasurementKey item)
{
if (m_inputMeasurementKeysHash != null)
return (m_inputMeasurementKeysHash.BinarySearch(item) >= 0);
// If no input measurements are defined we must assume user wants to accept all measurements - yikes!
return true;
}
///
/// Blocks the until the adapter is .
///
/// The number of milliseconds to wait.
/// true if the initialization succeeds; otherwise, false.
public virtual bool WaitForInitialize(int timeout)
{
if ((object)m_initializeWaitHandle != null)
return m_initializeWaitHandle.WaitOne(timeout);
return false;
}
///
/// Defines a temporal processing constraint for the 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 the adapter.")]
public virtual void SetTemporalConstraint(string startTime, string stopTime, string constraintParameters)
{
if (!startTime.IsNullOrWhiteSpace())
m_startTimeConstraint = ParseTimeTag(startTime);
else
m_startTimeConstraint = DateTime.MinValue;
if (!stopTime.IsNullOrWhiteSpace())
m_stopTimeConstraint = ParseTimeTag(stopTime);
else
m_stopTimeConstraint = DateTime.MaxValue;
}
///
/// Serves as a hash function for the current .
///
/// A hash code for the current .
public override int GetHashCode()
{
return m_hashCode;
}
///
/// 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.
///
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.
///
/// Processing .
protected virtual void OnProcessException(Exception ex)
{
if (ProcessException != null)
ProcessException(this, new EventArgs(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));
}
}
///
/// Safely increments the total processed measurements.
///
///
[MethodImpl(MethodImplOptions.Synchronized)]
protected virtual void IncrementProcessedMeasurements(long totalAdded)
{
// Check to see if total number of added points will exceed process interval used to show periodic
// messages of how many points have been archived so far...
int interval = MeasurementReportingInterval;
if (interval > 0)
{
bool showMessage = m_processedMeasurements + totalAdded >= (m_processedMeasurements / interval + 1) * interval;
m_processedMeasurements += totalAdded;
if (showMessage)
OnStatusMessage("{0:N0} measurements have been processed so far...", m_processedMeasurements);
}
}
private void GenHashCode()
{
// We cache hash code during construction or after element value change to speed usage
m_hashCode = (Name + ID.ToString()).GetHashCode();
}
#endregion
#region [ Static ]
// Static Fields
private static readonly Regex s_filterExpression = new Regex("(FILTER[ ]+(?\\w+)[ ]+WHERE[ ]+(?.+)[ ]+ORDER[ ]+BY[ ]+(?\\w+))|(FILTER[ ]+(?\\w+)[ ]+WHERE[ ]+(?.+))", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex s_timetagExpression = new Regex("\\*(?[+-]?\\d*\\.?\\d*)(?\\w+)", RegexOptions.Compiled);
// Static Methods
///
/// Parses a string formatted as an absolute or relative time tag.
///
/// String formatted as an absolute or relative time tag.
///
/// representing the parsed string formatted as an absolute or relative time tag.
///
///
///
/// Relative times are parsed based on an offset to current time (UTC) specified by "*".
///
///
/// The parameter 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 .
///
///
///
///
/// parameter cannot be null or empty.
/// does not contain a valid string representation of a date and time.
public static DateTime ParseTimeTag(string timetag)
{
if (timetag.IsNullOrWhiteSpace())
throw new ArgumentNullException("timetag", "Timetag string cannot be null or empty.");
DateTime dateTime;
if (timetag.Contains("*"))
{
// Relative time is specified.
// Examples:
// 1) * (Now)
// 2) *-20s (20 seconds ago)
// 3) *-10m (10 minutes ago)
// 4) *-1h (1 hour ago)
// 5) *-1d (1 day ago)
dateTime = DateTime.UtcNow;
timetag = timetag.RemoveWhiteSpace();
if (timetag.Length > 1)
{
Match timetagMatch;
lock (s_timetagExpression)
{
timetagMatch = s_timetagExpression.Match(timetag);
}
if (timetagMatch.Success)
{
double offset = double.Parse(timetagMatch.Result("${Offset}").Trim());
string unit = timetagMatch.Result("${Unit}").Trim().ToLower();
switch (unit[0])
{
case 's':
dateTime = dateTime.AddSeconds(offset);
break;
case 'm':
dateTime = dateTime.AddMinutes(offset);
break;
case 'h':
dateTime = dateTime.AddHours(offset);
break;
case 'd':
dateTime = dateTime.AddDays(offset);
break;
}
}
else
{
// Expression match failed, attempt to parse absolute time specification.
dateTime = DateTime.Parse(timetag, CultureInfo.InvariantCulture);
}
}
}
else
{
// Absolute time is specified.
dateTime = DateTime.Parse(timetag, CultureInfo.InvariantCulture);
}
return dateTime;
}
public static void LoadInputSourceIDs(IAdapter adapter)
{
LoadInputSourceIDs(adapter, "ActiveMeasurements");
}
///
/// Loads an or instance's input measurement keys from a specific set of source ID's.
///
/// to load input measurement keys for.
/// Measurement table name used to load input source ID's.
///
/// Any existing input measurement keys will be distinctly merged with those associated with specified source ID's.
///
public static void LoadInputSourceIDs(IAdapter adapter, string measurementTable)
{
string[] sourceIDs = null;
if (adapter is IOutputAdapter)
sourceIDs = (adapter as IOutputAdapter).InputSourceIDs;
else if (adapter is IActionAdapter)
sourceIDs = (adapter as IActionAdapter).InputSourceIDs;
if (sourceIDs != null)
{
// Attempt to lookup input measurement keys for given source IDs from default measurement table, if defined
try
{
if (adapter.DataSource.Tables.Contains(measurementTable))
{
StringBuilder likeExpression = new StringBuilder();
// Build like expression for each source ID
foreach (string sourceID in sourceIDs)
{
if (likeExpression.Length > 0)
likeExpression.Append(" OR ");
likeExpression.AppendFormat("ID LIKE '{0}:*'", sourceID);
}
DataRow[] filteredRows = adapter.DataSource.Tables[measurementTable].Select(likeExpression.ToString());
MeasurementKey[] sourceIDKeys = null;
if (filteredRows.Length > 0)
sourceIDKeys = filteredRows.Select(row => MeasurementKey.Parse(row["ID"].ToNonNullString(MeasurementKey.Undefined.ToString()), row["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType())).ToArray();
if (sourceIDKeys != null)
{
// Combine input measurement keys for source IDs with any existing input measurement keys and return unique set
if (adapter.InputMeasurementKeys == null)
adapter.InputMeasurementKeys = sourceIDKeys;
else
adapter.InputMeasurementKeys = sourceIDKeys.Concat(adapter.InputMeasurementKeys).Distinct().ToArray();
}
}
}
catch
{
// Errors here are not catastrophic, this simply limits the auto-assignment of input measurement keys based on specified source ID's
}
}
}
public static void LoadOutputSourceIDs(IAdapter adapter)
{
LoadOutputSourceIDs(adapter, "ActiveMeasurements");
}
///
/// Loads an or instance's output measurements from a specific set of source ID's.
///
/// to load output measurements for.
/// Measurement table name used to load output source ID's.
///
/// Any existing output measurements will be distinctly merged with those associated with specified source ID's.
///
public static void LoadOutputSourceIDs(IAdapter adapter, string measurementTable)
{
string[] sourceIDs = null;
if (adapter is IInputAdapter)
sourceIDs = (adapter as IInputAdapter).OutputSourceIDs;
else if (adapter is IActionAdapter)
sourceIDs = (adapter as IActionAdapter).OutputSourceIDs;
if (sourceIDs != null)
{
// Attempt to lookup output measurements for given source IDs from default measurement table, if defined
try
{
if (adapter.DataSource.Tables.Contains(measurementTable))
{
StringBuilder likeExpression = new StringBuilder();
// Build like expression for each source ID
foreach (string sourceID in sourceIDs)
{
if (likeExpression.Length > 0)
likeExpression.Append(" OR ");
likeExpression.AppendFormat("ID LIKE '{0}:*'", sourceID);
}
DataRow[] filteredRows = adapter.DataSource.Tables[measurementTable].Select(likeExpression.ToString());
MeasurementKey[] sourceIDKeys = null;
if (filteredRows.Length > 0)
sourceIDKeys = filteredRows.Select(row => MeasurementKey.Parse(row["ID"].ToNonNullString(MeasurementKey.Undefined.ToString()), row["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType())).ToArray();
if (sourceIDKeys != null)
{
List measurements = new List();
foreach (MeasurementKey key in sourceIDKeys)
{
// Create a new measurement for the provided field level information
Measurement measurement = new Measurement()
{
Key = key
};
// Attempt to lookup other associated measurement meta-data from default measurement table, if defined
try
{
if (adapter.DataSource.Tables.Contains(measurementTable))
{
filteredRows = adapter.DataSource.Tables[measurementTable].Select(string.Format("ID = '{0}'", key.ToString()));
if (filteredRows.Length > 0)
{
DataRow row = filteredRows[0];
measurement.ID = row["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType();
measurement.TagName = row["PointTag"].ToNonNullString();
// Attempt to update empty signal ID if available
if (measurement.Key.SignalID == Guid.Empty)
measurement.Key.UpdateSignalID(measurement.ID);
measurement.Multiplier = double.Parse(row["Multiplier"].ToString());
measurement.Adder = double.Parse(row["Adder"].ToString());
}
}
}
catch
{
// Errors here are not catastrophic, this simply limits the available meta-data
measurement.ID = Guid.Empty;
measurement.TagName = string.Empty;
}
measurements.Add(measurement);
}
// Combine output measurements for source IDs with any existing output measurements and return unique set
if (adapter.OutputMeasurements == null)
adapter.OutputMeasurements = measurements.ToArray();
else
adapter.OutputMeasurements = measurements.Concat(adapter.OutputMeasurements).Distinct().ToArray();
}
}
}
catch
{
// Errors here are not catastrophic, this simply limits the auto-assignment of input measurement keys based on specified source ID's
}
}
}
// Input keys can use DataSource for filtering desired set of input or output measurements
// based on any table and fields in the data set by using a filter expression instead of
// a list of measurement ID's. The format is as follows:
// FILTER WHERE [ORDER BY ]
// Source tables are expected to have at least the following fields:
//
// ID NVARCHAR Measurement key formatted as: ArchiveSource:PointID
// SignalID GUID Unique identification for measurement
// PointTag NVARCHAR Point tag of measurement
// Adder FLOAT Adder to apply to value, if any (default to 0.0)
// Multiplier FLOAT Multipler to apply to value, if any (default to 1.0)
//
// Could have used standard SQL syntax here but didn't want to give user the impression
// that this a standard SQL expression when it isn't - so chose the word FILTER to make
// consumer was aware that this was not SQL, but SQL "like". The WHERE clause expression
// uses standard SQL syntax (it is simply the DataTable.Select filter expression).
public static MeasurementKey[] ParseInputMeasurementKeys(DataSet dataSource, string value)
{
return ParseInputMeasurementKeys(dataSource, value, "ActiveMeasurements");
}
///
/// Parses input measurement keys from connection string setting.
///
/// The used to define input measurement keys.
/// Value of setting used to define input measurement keys, typically "inputMeasurementKeys".
/// Measurement table name used to load additional meta-data; this is not used when specifying a FILTER expression.
/// User selected input measurement keys.
public static MeasurementKey[] ParseInputMeasurementKeys(DataSet dataSource, string value, string measurementTable)
{
List keys = new List();
MeasurementKey key;
Guid id;
Match filterMatch = null;
bool dataSourceAvailable = ((object)dataSource != null);
value = value.Trim();
if (!value.IsNullOrWhiteSpace())
{
if (dataSourceAvailable)
{
lock (s_filterExpression)
{
filterMatch = s_filterExpression.Match(value);
}
}
if (dataSourceAvailable && filterMatch.Success)
{
string tableName = filterMatch.Result("${TableName}").Trim();
string expression = filterMatch.Result("${Expression}").Trim();
string sortField = filterMatch.Result("${SortField}").Trim();
foreach (DataRow row in dataSource.Tables[tableName].Select(expression, sortField))
{
if (MeasurementKey.TryParse(row["ID"].ToString(), row["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType(), out key))
keys.Add(key);
}
}
else
{
// Add manually defined measurement keys
foreach (string item in value.Split(';'))
{
if (!item.IsNullOrWhiteSpace())
{
if (MeasurementKey.TryParse(item, Guid.Empty, out key))
{
// Attempt to update empty signal ID if available
if (dataSourceAvailable && key.SignalID == Guid.Empty)
{
if (dataSource.Tables.Contains(measurementTable))
{
DataRow[] filteredRows = dataSource.Tables[measurementTable].Select(string.Format("ID = '{0}'", key.ToString()));
if (filteredRows.Length > 0)
key.SignalID = filteredRows[0]["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType();
}
}
keys.Add(key);
}
else if (Common.TryParseGuid(item, out id))
{
if (dataSourceAvailable && dataSource.Tables.Contains(measurementTable))
{
DataRow[] filteredRows = dataSource.Tables[measurementTable].Select(string.Format("SignalID = '{0}'", id));
if (filteredRows.Length > 0 && MeasurementKey.TryParse(filteredRows[0]["ID"].ToString(), id, out key))
keys.Add(key);
}
else
{
keys.Add(MeasurementKey.LookupBySignalID(id));
}
}
else
{
// Attempt to update empty signal ID if available
if (dataSourceAvailable && dataSource.Tables.Contains(measurementTable))
{
DataRow[] filteredRows = dataSource.Tables[measurementTable].Select(string.Format("PointTag = '{0}'", item));
if (filteredRows.Length > 0)
{
key = MeasurementKey.LookupBySignalID(filteredRows[0]["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType());
keys.Add(key);
}
}
if (key == default(MeasurementKey))
{
throw new InvalidOperationException(string.Format("Could not parse input measurement definition \"{0}\" as a filter expression, measurement key, point tag or Guid", item));
}
}
}
}
}
}
return keys.ToArray();
}
public static MeasurementKey[] ParseOutputMeasurementKeys(DataSet dataSource, string value)
{
return ParseOutputMeasurementKeys(dataSource, value, "ActiveMeasurements");
}
///
/// Parses output measurement keys from connection string setting.
///
/// The used to define output measurements.
/// Value of setting used to define output measurements, typically "outputMeasurements".
/// Measurement table name used to load additional meta-data; this is not used when specifying a FILTER expression.
/// User selected output measurements.
public static MeasurementKey[] ParseOutputMeasurementKeys(DataSet dataSource, string value, string measurementTable)
{
return ParseOutputMeasurements(dataSource, value, measurementTable).MeasurementKeys().ToArray();
}
public static IMeasurement[] ParseOutputMeasurements(DataSet dataSource, string value)
{
return ParseOutputMeasurements(dataSource, value, "ActiveMeasurements");
}
///
/// Parses output measurements from connection string setting.
///
/// The used to define output measurements.
/// Value of setting used to define output measurements, typically "outputMeasurements".
/// Measurement table name used to load additional meta-data; this is not used when specifying a FILTER expression.
/// User selected output measurements.
public static IMeasurement[] ParseOutputMeasurements(DataSet dataSource, string value, string measurementTable)
{
List measurements = new List();
Measurement measurement;
MeasurementKey key;
Match filterMatch = null;
Guid id;
bool dataSourceAvailable = ((object)dataSource != null);
value = value.Trim();
if (!value.IsNullOrWhiteSpace())
{
if (dataSourceAvailable)
{
lock (s_filterExpression)
{
filterMatch = s_filterExpression.Match(value);
}
}
if (dataSourceAvailable && filterMatch.Success)
{
string tableName = filterMatch.Result("${TableName}").Trim();
string expression = filterMatch.Result("${Expression}").Trim();
string sortField = filterMatch.Result("${SortField}").Trim();
foreach (DataRow row in dataSource.Tables[tableName].Select(expression, sortField))
{
id = row["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType();
measurement = new Measurement()
{
ID = id,
Key = MeasurementKey.Parse(row["ID"].ToString(), id),
TagName = row["PointTag"].ToNonNullString(),
Adder = double.Parse(row["Adder"].ToString()),
Multiplier = double.Parse(row["Multiplier"].ToString())
};
measurements.Add(measurement);
}
}
else
{
string[] elem;
double adder, multipler;
foreach (string item in value.Split(';'))
{
if (!item.IsNullOrWhiteSpace())
{
elem = item.Trim().Split(',');
if (!MeasurementKey.TryParse(elem[0], Guid.Empty, out key))
{
if (Common.TryParseGuid(item, out id))
{
if (dataSourceAvailable && dataSource.Tables.Contains(measurementTable))
{
DataRow[] filteredRows = dataSource.Tables[measurementTable].Select(string.Format("SignalID = '{0}'", id));
if (filteredRows.Length > 0)
MeasurementKey.TryParse(filteredRows[0]["ID"].ToString(), id, out key);
}
else
{
key = MeasurementKey.LookupBySignalID(id);
}
}
else
{
// Attempt to update empty signal ID if available
if (dataSourceAvailable && dataSource.Tables.Contains(measurementTable))
{
DataRow[] filteredRows = dataSource.Tables[measurementTable].Select(string.Format("PointTag = '{0}'", item));
if (filteredRows.Length > 0)
{
key = MeasurementKey.LookupBySignalID(filteredRows[0]["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType());
}
}
if (key == default(MeasurementKey))
{
throw new InvalidOperationException(string.Format("Could not parse output measurement definition \"{0}\" as a filter expression, measurement key, point tag or Guid", item));
}
}
}
// Adder and multipler may be optionally specified
if (elem.Length > 1)
{
if (!double.TryParse(elem[1].Trim(), out adder))
adder = 0.0D;
}
else
{
adder = 0.0D;
}
if (elem.Length > 2)
{
if (!double.TryParse(elem[2].Trim(), out multipler))
multipler = 1.0D;
}
else
{
multipler = 1.0D;
}
// Create a new measurement for the provided field level information
measurement = new Measurement()
{
ID = key.SignalID,
Key = key,
Adder = adder,
Multiplier = multipler
};
// Attempt to lookup other associated measurement meta-data from default measurement table, if defined
try
{
if (dataSourceAvailable && dataSource.Tables.Contains(measurementTable))
{
DataRow[] filteredRows = dataSource.Tables[measurementTable].Select(string.Format("ID = '{0}'", key.ToString()));
if (filteredRows.Length > 0)
{
DataRow row = filteredRows[0];
measurement.ID = row["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType();
measurement.TagName = row["PointTag"].ToNonNullString();
// Attempt to update empty signal ID if available
if (measurement.Key.SignalID == Guid.Empty)
measurement.Key.UpdateSignalID(measurement.ID);
// Manually specified adder and multiplier take precedence, but if none were specified,
// then those defined in the meta-data are used instead
if (elem.Length < 3)
measurement.Multiplier = double.Parse(row["Multiplier"].ToString());
if (elem.Length < 2)
measurement.Adder = double.Parse(row["Adder"].ToString());
}
}
}
catch
{
// Errors here are not catastrophic, this simply limits the available meta-data
measurement.TagName = string.Empty;
}
measurements.Add(measurement);
}
}
}
}
return measurements.ToArray();
}
#endregion
}
}