548 lines · 24.2 KB · C#
RawDownload
//******************************************************************************************************//  MeasurementKey.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.Concurrent;using System.Data;using System.Threading;using GSF.Data; namespace GSF.TimeSeries{    /// <summary>    /// Represents a primary key for a measurement.    /// </summary>    [Serializable]    public class MeasurementKey    {        #region [ Members ]         // Fields        private readonly Guid m_signalID;        private ulong m_id;        private string m_source;        private readonly int m_hashCode;        private readonly int m_runtimeID;        private MeasurementMetadata m_metadata;         #endregion         #region [ Constructors ]         private MeasurementKey(Guid signalID, ulong id, string source)        {            m_signalID = signalID;            m_id = id;            m_source = source;            m_hashCode = base.GetHashCode();            m_runtimeID = Interlocked.Increment(ref s_nextRuntimeID) - 1; // Returns the incremented value. Hints the -1            m_metadata = new MeasurementMetadata(this, null, 0, 1, null);        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets or sets <see cref="Guid"/> ID of signal associated with this <see cref="MeasurementKey"/>.        /// </summary>        public Guid SignalID => m_signalID;         /// <summary>        /// Gets or sets the numeric ID of this <see cref="MeasurementKey"/>.        /// </summary>        public ulong ID => m_id;         /// <summary>        /// Gets or sets the source of this <see cref="MeasurementKey"/>.        /// </summary>        /// <remarks>        /// This value is typically used to track the archive name in which the measurement, that this <see cref="MeasurementKey"/> represents, is stored.        /// </remarks>        public string Source => m_source;         /// <summary>        /// A unique ID that is assigned at runtime to identify this instance of <see cref="MeasurementKey"/>.         /// This value will change between life cycles, so it cannot be used to compare <see cref="MeasurementKey"/>        /// instances that are running out of process or in a separate <see cref="AppDomain"/>.        /// </summary>        /// <remarks>        /// Since each <see cref="SignalID"/> is only tied to a single <see cref="MeasurementKey"/> object,         /// this provides another unique identifier that is zero indexed.         /// This allows certain optimizations such as array lookups.        /// </remarks>        public int RuntimeID => m_runtimeID;         /// <summary>        /// Gets the <see cref="TimeSeries.MeasurementMetadata"/> as they appear in the primary <see cref="Adapters.IAdapter.DataSource"/>.        /// </summary>        /// <remarks>        /// This is to be considered the reference value. Adapters are free to change this inside specific <see cref="IMeasurement"/> instances        /// This value should only be updated upon change in the primary data source using <see cref="SetMeasurementMetadata"/>.        /// </remarks>        public MeasurementMetadata Metadata => m_metadata;         #endregion         #region [ Methods ]         /// <summary>        /// Updates the values of the <see cref="Metadata"/>.        /// </summary>        /// <param name="tagName">Gets or sets the text based tag name.</param>        /// <param name="adder">Defines an offset to add to the <see cref="IMeasurement"/> value.</param>        /// <param name="multiplier">Defines a multiplicative offset to apply to the <see cref="IMeasurement"/> value.</param>        /// <param name="valueFilter">Defines the <see cref="MeasurementValueFilterFunction"/> to use when downsampling this type of <see cref="IMeasurement"/> value.</param>        public void SetMeasurementMetadata(string tagName, double adder, double multiplier, MeasurementValueFilterFunction valueFilter)        {            if (this == Undefined)                throw new NotSupportedException("Cannot set data source information for an undefined measurement.");             if (m_metadata.TagName != tagName || m_metadata.Adder != adder || m_metadata.Multiplier != multiplier || m_metadata.MeasurementValueFilter != valueFilter)                m_metadata = new MeasurementMetadata(this, tagName, adder, multiplier, valueFilter);        }         /// <summary>        /// Returns a <see cref="string"/> that represents the current <see cref="MeasurementKey"/>.        /// </summary>        /// <returns>A <see cref="string"/> that represents the current <see cref="MeasurementKey"/>.</returns>        public override string ToString()        {            return $"{m_source}:{m_id}";        }         /// <summary>        /// Serves as a hash function for the current <see cref="MeasurementKey"/>.        /// </summary>        /// <returns>A hash code for the current <see cref="MeasurementKey"/>.</returns>        public override int GetHashCode()        {            return m_hashCode;        }         #endregion         #region [ Static ]         // Static Fields         // All edits to <see cref="s_idCache"/> as well as the ConcurrentDictionaries in s_keyCache should occur within a lock on s_syncEdits        private static readonly ConcurrentDictionary<Guid, MeasurementKey> s_idCache = new ConcurrentDictionary<Guid, MeasurementKey>();        private static readonly ConcurrentDictionary<string, ConcurrentDictionary<ulong, MeasurementKey>> s_keyCache = new ConcurrentDictionary<string, ConcurrentDictionary<ulong, MeasurementKey>>(StringComparer.OrdinalIgnoreCase);        private static readonly object s_syncEdits = new object();        private static int s_nextRuntimeID;         /// <summary>        /// Represents an undefined measurement key.        /// </summary>        public static readonly MeasurementKey Undefined;         // Static Constructor        static MeasurementKey()        {            // Order of operations is critical here since MeasurementKey and MeasurementMetadata have a circular reference            Undefined = CreateUndefinedMeasurementKey();            MeasurementMetadata.CreateUndefinedMeasurementMetadata();            Undefined.m_metadata = MeasurementMetadata.Undefined;        }         // Static Methods         /// <summary>        /// Constructs a new <see cref="MeasurementKey"/> given the specified parameters.        /// </summary>        /// <param name="signalID"><see cref="Guid"/> ID of associated signal, if defined.</param>        /// <param name="value">A string representation of the <see cref="MeasurementKey"/>.</param>        /// <exception cref="ArgumentException"><paramref name="signalID"/> cannot be empty</exception>        /// <exception cref="FormatException">The value is not in the correct format for a <see cref="MeasurementKey"/> value.</exception>        public static MeasurementKey CreateOrUpdate(Guid signalID, string value)        {            string source;            ulong id;             if (!TrySplit(value, out source, out id))                throw new FormatException("The value is not in the correct format for a MeasurementKey value");             return CreateOrUpdate(signalID, source, id);        }         /// <summary>        /// Constructs a new <see cref="MeasurementKey"/> given the specified parameters.        /// </summary>        /// <param name="signalID"><see cref="Guid"/> ID of associated signal, if defined.</param>        /// <param name="source">Source of the measurement that this <see cref="MeasurementKey"/> represents (e.g., name of archive).</param>        /// <param name="id">Numeric ID of the measurement that this <see cref="MeasurementKey"/> represents.</param>        /// <exception cref="ArgumentException"><paramref name="signalID"/> cannot be empty.</exception>        /// <exception cref="ArgumentNullException"><paramref name="source"/> cannot be null.</exception>        public static MeasurementKey CreateOrUpdate(Guid signalID, string source, ulong id)        {            Func<Guid, MeasurementKey> addValueFactory;            Func<Guid, MeasurementKey, MeasurementKey> updateValueFactory;             if (signalID == Guid.Empty)                throw new ArgumentException("Unable to update undefined measurement key", nameof(signalID));             if (string.IsNullOrWhiteSpace(source))                throw new ArgumentNullException(nameof(source), "MeasurementKey source cannot be null or empty");             addValueFactory = guid =>            {                // Create a new measurement key and add it to the KeyCache                ConcurrentDictionary<ulong, MeasurementKey> idLookup = s_keyCache.GetOrAdd(source, s => new ConcurrentDictionary<ulong, MeasurementKey>());                return idLookup[id] = new MeasurementKey(guid, id, source);            };             updateValueFactory = (guid, key) =>            {                ConcurrentDictionary<ulong, MeasurementKey> idLookup;                 // If existing measurement key is exactly the same as the                // one we are trying to create, simply return that key                if (key.ID == id && key.Source == source)                    return key;                 // Update source and ID and re-insert it into the KeyCache                key.m_source = source;                key.m_id = id;                 idLookup = s_keyCache.GetOrAdd(source, s => new ConcurrentDictionary<ulong, MeasurementKey>());                idLookup[id] = key;                 return key;            };             // https://msdn.microsoft.com/en-us/library/ee378675(v=vs.110).aspx            //            //     If you call AddOrUpdate simultaneously on different threads,            //     addValueFactory may be called multiple times, but its key/value            //     pair might not be added to the dictionary for every call.            //            // This lock prevents race conditions that might occur in the addValueFactory that            // could cause different MeasurementKey objects to be written to the KeyCache and IDCache            lock (s_syncEdits)            {                return s_idCache.AddOrUpdate(signalID, addValueFactory, updateValueFactory);            }        }         /// <summary>        /// Constructs a new <see cref="MeasurementKey"/> given the specified parameters.        /// </summary>        /// <param name="signalID"><see cref="Guid"/> ID of associated signal, if defined.</param>        /// <param name="value">A string representation of the <see cref="MeasurementKey"/>.</param>        /// <param name="key">The measurement key that was created or updated or <see cref="Undefined"/>.</param>        /// <returns>True if the measurement key was successfully created or updated, false otherwise.</returns>        /// <exception cref="ArgumentException"><paramref name="signalID"/> cannot be empty.</exception>        /// <exception cref="ArgumentNullException">Measurement key Source cannot be null.</exception>        public static bool TryCreateOrUpdate(Guid signalID, string value, out MeasurementKey key)        {            try            {                key = CreateOrUpdate(signalID, value);                return true;            }            catch            {                key = Undefined;                return false;            }        }         /// <summary>        /// Constructs a new <see cref="MeasurementKey"/> given the specified parameters.        /// </summary>        /// <param name="signalID"><see cref="Guid"/> ID of associated signal, if defined.</param>        /// <param name="source">Source of the measurement that this <see cref="MeasurementKey"/> represents (e.g., name of archive).</param>        /// <param name="id">Numeric ID of the measurement that this <see cref="MeasurementKey"/> represents.</param>        /// <param name="key">The measurement key that was created or updated or <see cref="Undefined"/>.</param>        /// <returns>True if the measurement key was successfully created or updated, false otherwise.</returns>        /// <exception cref="ArgumentException"><paramref name="signalID"/> cannot be empty.</exception>        /// <exception cref="ArgumentNullException"><paramref name="source"/> cannot be null.</exception>        public static bool TryCreateOrUpdate(Guid signalID, string source, ulong id, out MeasurementKey key)        {            try            {                key = CreateOrUpdate(signalID, source, id);                return true;            }            catch            {                key = Undefined;                return false;            }        }         /// <summary>        /// Looks up the measurement key associated with the given signal ID.        /// </summary>        /// <param name="signalID">The signal ID of the measurement key.</param>        /// <returns>The measurement key associated with the given signal ID.</returns>        /// <remarks>        /// If no measurement key is found with the given signal ID,        /// this method returns <see cref="Undefined"/>.        /// </remarks>        public static MeasurementKey LookUpBySignalID(Guid signalID)        {            MeasurementKey key;             // ReSharper disable once InconsistentlySynchronizedField            if (signalID == Guid.Empty)                key = Undefined;            else if (!s_idCache.TryGetValue(signalID, out key))                key = Undefined;             return key;        }         /// <summary>        /// Looks up the measurement key associated with the given source and ID.        /// </summary>        /// <param name="source">The source of the signal.</param>        /// <param name="id">The source-specific unique integer identifier.</param>        /// <returns>The measurement key associated with the given source and ID.</returns>        /// <remarks>        /// If no measurement key is found with the given source and ID,        /// this method returns <see cref="Undefined"/>.        /// </remarks>        public static MeasurementKey LookUpBySource(string source, ulong id)        {            ConcurrentDictionary<ulong, MeasurementKey> idLookup;            MeasurementKey key;             if (!s_keyCache.TryGetValue(source, out idLookup))                return Undefined;             if (!idLookup.TryGetValue(id, out key))                return Undefined;             return key;        }         /// <summary>        /// Performs a lookup by signal ID and, failing that, attempts to create        /// the key using the given signal ID and the parsed source, and ID.        /// </summary>        /// <param name="signalID">The signal ID of the key to be looked up.</param>        /// <param name="value">A string representation of the <see cref="MeasurementKey"/>.</param>        /// <returns>        /// If the lookup succeeds, an existing measurement key with a matching signalID.        /// If creation succeeds, a new measurement key with matching signal ID, source, and ID.        /// Otherwise, <see cref="Undefined"/>.        /// </returns>        public static MeasurementKey LookUpOrCreate(Guid signalID, string value)        {            string source;            ulong id;             if (!TrySplit(value, out source, out id))                return LookUpOrCreate(signalID, Undefined.Source, Undefined.ID);             return LookUpOrCreate(signalID, source, id);        }         /// <summary>        /// Performs a lookup by signal ID and, failing that, attempts to        /// create the key using the given signal ID, source, and ID.        /// </summary>        /// <param name="signalID">The signal ID of the key to be looked up.</param>        /// <param name="source">The source to use for the key if the lookup fails.</param>        /// <param name="id">The ID to use for the key if the lookup fails.</param>        /// <returns>        /// If the lookup succeeds, an existing measurement key with a matching signalID.        /// If creation succeeds, a new measurement key with matching signal ID, source, and ID.        /// Otherwise, <see cref="Undefined"/>.        /// </returns>        public static MeasurementKey LookUpOrCreate(Guid signalID, string source, ulong id)        {            MeasurementKey key = LookUpBySignalID(signalID);             if (key == Undefined && !TryCreateOrUpdate(signalID, source, id, out key))                return Undefined;             return key;        }         /// <summary>        /// Performs a lookup by source and, failing that, attempts to create        /// the key using a newly generated signal ID and the parsed source and ID.        /// </summary>        /// <param name="value">A string representation of the <see cref="MeasurementKey"/>.</param>        /// <returns>        /// If the lookup succeeds, an existing measurement key with a matching signalID.        /// If creation succeeds, a new measurement key with matching signal ID, source, and ID.        /// Otherwise, <see cref="Undefined"/>.        /// </returns>        public static MeasurementKey LookUpOrCreate(string value)        {            string source;            ulong id;             if (!TrySplit(value, out source, out id))                return Undefined;             return LookUpOrCreate(source, id);        }         /// <summary>        /// Performs a lookup by source and, failing that, attempts to create        /// the key using a newly generated signal ID and the given source and ID.        /// </summary>        /// <param name="source">The source to use for the key if the lookup fails.</param>        /// <param name="id">The ID to use for the key if the lookup fails.</param>        /// <returns>        /// If the lookup succeeds, an existing measurement key with a matching signalID.        /// If creation succeeds, a new measurement key with matching signal ID, source, and ID.        /// Otherwise, <see cref="Undefined"/>.        /// </returns>        public static MeasurementKey LookUpOrCreate(string source, ulong id)        {            MeasurementKey key = LookUpBySource(source, id);             if (key == Undefined && !TryCreateOrUpdate(Guid.NewGuid(), source, id, out key))                return Undefined;             return key;        }         /// <summary>        /// Converts the string representation of a <see cref="MeasurementKey"/> into its value equivalent.        /// </summary>        /// <param name="value">A string representing the <see cref="MeasurementKey"/> to convert.</param>        /// <returns>A <see cref="MeasurementKey"/> value equivalent the representation contained in <paramref name="value"/>.</returns>        /// <exception cref="FormatException">The value is not in the correct format for a <see cref="MeasurementKey"/> value.</exception>        public static MeasurementKey Parse(string value)        {            MeasurementKey key;             if (TryParse(value, out key))                return key;             throw new FormatException("The value is not in the correct format for a MeasurementKey value");        }         /// <summary>        /// Attempts to convert the string representation of a <see cref="MeasurementKey"/> into its value equivalent.        /// </summary>        /// <param name="value">A string representing the <see cref="MeasurementKey"/> to convert.</param>        /// <param name="key">Output <see cref="MeasurementKey"/> in which to stored parsed value.</param>        /// <returns>A <c>true</c> if <see cref="MeasurementKey"/>representation contained in <paramref name="value"/> could be parsed; otherwise <c>false</c>.</returns>        public static bool TryParse(string value, out MeasurementKey key)        {            string source;            ulong id;             // Split the input into source and ID            if (TrySplit(value, out source, out id))            {                // First attempt to look up an existing key                key = LookUpBySource(source, id);                 if (key == Undefined)                {                    try                    {                        // Lookup failed - attempt to create it with a newly generated signal ID                        key = CreateOrUpdate(Guid.NewGuid(), source, id);                    }                    catch                    {                        // source is null or empty                        key = Undefined;                    }                }            }            else            {                // Incorrect format for a measurement key                key = Undefined;            }             return key != Undefined;        }         /// <summary>        /// Establish default <see cref="MeasurementKey"/> cache.        /// </summary>        /// <param name="connection">The database connection.</param>        /// <param name="adapterType">The database adapter type.</param>        /// <param name="measurementTable">Measurement table name used to load measurement key cache.</param>        /// <remarks>        /// Source tables are expected to have at least the following fields:        /// <code>        ///      ID          NVARCHAR    Measurement key formatted as: ArchiveSource:PointID        ///      SignalID    GUID        Unique identification for measurement        /// </code>        /// </remarks>        public static void EstablishDefaultCache(IDbConnection connection, Type adapterType, string measurementTable = "ActiveMeasurement")        {            string source;

Lines 1–500 of 548.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t