981 lines · 56.0 KB · C#
RawDownload
//******************************************************************************************************//  MetadataImportAdapter.cs - Gbtc////  Copyright © 2018, Grid Protection Alliance.  All Rights Reserved.////  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See//  the NOTICE file distributed with this work for additional information regarding copyright ownership.//  The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this//  file except in compliance with the License. You may obtain a copy of the License at:////      http://opensource.org/licenses/MIT////  Unless agreed to in writing, the subject software distributed under the License is distributed on an//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the//  License for the specific language governing permissions and limitations.////  Code Modification History://  ----------------------------------------------------------------------------------------------------//  02/24/2018 - Stephen C. Wills//       Generated original version of source code.////****************************************************************************************************** using GSF;using GSF.Collections;using GSF.Configuration;using GSF.Data;using GSF.Diagnostics;using GSF.Scheduling;using GSF.Threading;using GSF.TimeSeries;using GSF.TimeSeries.Adapters;using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.IO;using System.Linq;using System.Threading; namespace MetadataAdapters{    /// <summary>    /// Represents an adapter that reads metadata from a file and updates the database.    /// </summary>    [Description("Metadata Import: Reads metadata from a file and updates the database.")]    public class MetadataImportAdapter : FacileActionAdapterBase    {        #region [ Members ]         // Constants         /// <summary>        /// Defines the default value for the <see cref="ImportSchedule"/> property.        /// </summary>        public const string DefaultImportSchedule = "*/5 * * * *";         /// <summary>        /// Defines the default value for the <see cref="UseTransactionForMetadata"/> property.        /// </summary>        public const bool DefaultUseTransactionForMetadata = true;         /// <summary>        /// Defines the default value for the <see cref="MetadataSynchronizationTimeout"/> property.        /// </summary>        public const int DefaultMetadataSynchronizationTimeout = 0;         /// <summary>        /// Defines the default value for the <see cref="UseSourcePrefixNames"/> property.        /// </summary>        public const bool DefaultUseSourcePrefixNames = false;         private const string ScheduleName = nameof(MetadataImportAdapter);         // Fields        private Action<DataSet> m_synchronizeMetadataAction;        private ScheduleManager m_scheduleManager;        private DateTime m_lastMetaDataRefreshTime;        private bool m_disposed;         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new instance of the <see cref="MetadataImportAdapter"/> class.        /// </summary>        public MetadataImportAdapter()        {            m_synchronizeMetadataAction = GetSynchronizeMetadataAction();            m_scheduleManager = new ScheduleManager();            m_scheduleManager.ScheduleDue += ScheduleManager_ScheduleDue;        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets or sets the path to the file in which the metadata to be imported will be defined.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the path to the file in which the metadata to be imported will be defined.")]        public string ImportFilePath { get; set; }         /// <summary>        /// Gets or sets the schedule, using cron syntax, to search for metadata files to import.        /// </summary>        [ConnectionStringParameter]        [DefaultValue(DefaultImportSchedule)]        [Description("Defines the schedule, using cron syntax, to search for metadata files to import.")]        public string ImportSchedule { get; set; }         /// <summary>        /// Gets or sets the acronym of the device used as the parent of the devices defined in the metadata.        /// </summary>        [ConnectionStringParameter]        [DefaultValue("")]        [Description("Defines the acronym of the device used as the parent of the devices defined in the metadata.")]        public string ParentDeviceAcronym { get; set; }         /// <summary>        /// Gets or sets flag that determines if metadata synchronization should be performed within a transaction.        /// </summary>        [ConnectionStringParameter]        [DefaultValue(DefaultUseTransactionForMetadata)]        [Description("Defines the flag that determines if metadata synchronization should be performed within a transaction.")]        public bool UseTransactionForMetadata { get; set; }         /// <summary>        /// Gets or sets the timeout used when executing database queries during metadata synchronization.        /// </summary>        [ConnectionStringParameter]        [DefaultValue(DefaultMetadataSynchronizationTimeout)]        [Description("Defines the timeout used when exeucting database queries during metadata synchronization.")]        public int MetadataSynchronizationTimeout { get; set; }         /// <summary>        /// Gets or sets flag that determines if child devices associated with a subscription        /// should be prefixed with the subscription name and an exclamation point to ensure        /// device name uniqueness - recommended value is <c>true</c>.        /// </summary>        [ConnectionStringParameter]        [DefaultValue(DefaultUseSourcePrefixNames)]        [Description("Defines the flag that determines if child device acronyms should be prefixed with the parent acronym.")]        public bool UseSourcePrefixNames { get; set; }         /// <summary>        /// Gets the flag indicating if this adapter supports temporal processing.        /// </summary>        public override bool SupportsTemporalProcessing => false;         /// <summary>        /// Gets or sets primary keys of input measurements the <see cref="MetadataImportAdapter"/> expects, if any.        /// </summary>        [EditorBrowsable(EditorBrowsableState.Never)]        public new MeasurementKey[] InputMeasurementKeys        {            get            {                return base.InputMeasurementKeys;            }            set            {                base.InputMeasurementKeys = value;            }        }         /// <summary>        /// Gets or sets output measurements that the <see cref="MetadataImportAdapter"/> will produce, if any.        /// </summary>        [EditorBrowsable(EditorBrowsableState.Never)]        public new IMeasurement[] OutputMeasurements        {            get            {                return base.OutputMeasurements;            }            set            {                base.OutputMeasurements = value;            }        }         /// <summary>        /// Gets or sets the frames per second to be used by the <see cref="MetadataImportAdapter"/>.        /// </summary>        [EditorBrowsable(EditorBrowsableState.Never)]        public new int FramesPerSecond        {            get            {                return base.FramesPerSecond;            }            set            {                base.FramesPerSecond = value;            }        }         /// <summary>        /// Gets or sets the allowed past time deviation tolerance, in seconds (can be sub-second).        /// </summary>        [EditorBrowsable(EditorBrowsableState.Never)]        public new double LagTime        {            get            {                return base.LagTime;            }            set            {                base.LagTime = value;            }        }         /// <summary>        /// Gets or sets the allowed future time deviation tolerance, in seconds (can be sub-second).        /// </summary>        [EditorBrowsable(EditorBrowsableState.Never)]        public new double LeadTime        {            get            {                return base.LeadTime;            }            set            {                base.LeadTime = value;            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Initializes <see cref="MetadataImportAdapter"/>.        /// </summary>        public override void Initialize()        {            base.Initialize();             ConnectionStringParser<ConnectionStringParameterAttribute> parser = new ConnectionStringParser<ConnectionStringParameterAttribute>();            parser.ParseConnectionString(ConnectionString, this);             if (string.IsNullOrWhiteSpace(ParentDeviceAcronym))                ParentDeviceAcronym = Name + "!PARENT";             // Default to not using transactions for metadata on SQL server (helps avoid deadlocks)            if (!Settings.ContainsKey(nameof(UseTransactionForMetadata)))            {                using (AdoDataConnection database = new AdoDataConnection("systemSettings"))                {                    if (database.IsSQLServer)                        UseTransactionForMetadata = false;                }            }             m_scheduleManager.AddSchedule(ScheduleName, ImportSchedule);        }         /// <summary>        /// Starts the <see cref="MetadataImportAdapter"/> or restarts it if it is already running.        /// </summary>        public override void Start()        {            base.Start();            m_scheduleManager.Start();        }         /// <summary>        /// Stops the <see cref="MetadataImportAdapter"/>.        /// </summary>        public override void Stop()        {            base.Stop();            m_scheduleManager.Stop();        }         /// <summary>        /// Searches for metadata to be imported and imports the metadata if found.        /// </summary>        [AdapterCommand("Imports metadata if found at the target location.")]        public void ImportMetadata()        {            if (!File.Exists(ImportFilePath))                return;             const int MaxFailedAttempts = 5;            const int DelayAfterFailure = 2000;            int failedAttempts = 0;             while (true)            {                try                {                    DataSet metadata;                     using (FileStream stream = File.OpenRead(ImportFilePath))                    {                        metadata = stream.DeserializeToDataSet();                    }                     m_synchronizeMetadataAction(metadata);                    File.Delete(ImportFilePath);                    break;                }                catch (IOException)                {                    // Only throw an IOException                    // after five failed attempts                    failedAttempts++;                     if (failedAttempts >= MaxFailedAttempts)                        throw;                     Thread.Sleep(DelayAfterFailure);                }            }        }         /// <summary>        /// Gets a short one-line status of this <see cref="MetadataExportAdapter"/>.        /// </summary>        /// <param name="maxLength">Maximum number of available characters for display.</param>        /// <returns>A short one-line summary of the current status of this <see cref="MetadataExportAdapter"/>.</returns>        public override string GetShortStatus(int maxLength)        {            string timestampFormat = "HH:mm:ss";             if (DateTime.Now.Subtract(m_lastMetaDataRefreshTime).TotalDays > 1.0D)                timestampFormat = "yyyy-MM-dd HH:mm";             string lastImport = m_lastMetaDataRefreshTime.ToString(timestampFormat);            string shortStatus = $"Last import: {m_lastMetaDataRefreshTime}";            return shortStatus.CenterText(maxLength);        }         /// <summary>        /// Releases the unmanaged resources used by the <see cref="MetadataImportAdapter"/> object and optionally releases the managed resources.        /// </summary>        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>        protected override void Dispose(bool disposing)        {            if (!m_disposed)            {                try                {                    if (disposing)                    {                        m_scheduleManager.Stop();                        m_scheduleManager.Dispose();                    }                }                finally                {                    m_disposed = true;          // Prevent duplicate dispose.                    base.Dispose(disposing);    // Call base class Dispose().                }            }        }         /// <summary>        /// Handles metadata synchronization to local system.        /// </summary>        private Action<DataSet> GetSynchronizeMetadataAction()        {            DataSet metadataToSynchronize = default(DataSet);            DataSet synchronizedMetadata = default(DataSet);            Guid nodeID = default(Guid);            int virtualProtocolID = default(int);            long syncProgressTotalActions = default(long);            long syncProgressActionsCount = default(long);            long syncProgressUpdateInterval = default(long);            long syncProgressLastMessage = default(long);             Action<long> initSyncProgress = totalActions =>            {                syncProgressTotalActions = totalActions;                syncProgressActionsCount = 0;                 // We update user on progress with 5 messages or every 15 seconds                syncProgressUpdateInterval = (long)(totalActions * 0.2D);                syncProgressLastMessage = DateTime.UtcNow.Ticks;            };             Action updateSyncProgress = () =>            {                syncProgressActionsCount++;                 if (syncProgressActionsCount % syncProgressUpdateInterval == 0 || DateTime.UtcNow.Ticks - syncProgressLastMessage > 150000000)                {                    OnStatusMessage(MessageLevel.Info, $"metadata synchronization is {syncProgressActionsCount / (double)syncProgressTotalActions:0.0%} complete...");                    syncProgressLastMessage = DateTime.UtcNow.Ticks;                }            };             Func<DataSet, bool> synchronizedMetadataChanged = newSynchronizedMetadata =>            {                try                {                    return !DataSetEqualityComparer.Default.Equals(synchronizedMetadata, newSynchronizedMetadata);                }                catch                {                    return true;                }            };             Action synchronizeMetadata = () =>            {                DataSet metadata = metadataToSynchronize;                 // Only perform database synchronization if metadata has changed since last update                if (!synchronizedMetadataChanged(metadata))                    return;                 if ((object)metadata == null)                {                    OnStatusMessage(MessageLevel.Error, "metadata synchronization was not performed, deserialized dataset was empty.");                    return;                }                 // Track total metadata synchronization process time                Ticks startTime = DateTime.UtcNow.Ticks;                DateTime updateTime;                DateTime latestUpdateTime = DateTime.MinValue;                 // Open the configuration database using settings found in the config file                using (AdoDataConnection database = new AdoDataConnection("systemSettings"))                using (IDbCommand command = database.Connection.CreateCommand())                {                    IDbTransaction transaction = null;                     if (UseTransactionForMetadata)                        transaction = database.Connection.BeginTransaction(database.DefaultIsloationLevel);                     try                    {                        if ((object)transaction != null)                            command.Transaction = transaction;                         // Determine the active node ID - we cache this since this value won't change for the lifetime of this class                        if (nodeID == Guid.Empty)                            nodeID = Guid.Parse(command.ExecuteScalar($"SELECT NodeID FROM IaonActionAdapter WHERE ID = {(int)ID}", MetadataSynchronizationTimeout).ToString());                         // Determine the protocol record auto-inc ID value for the gateway transport protocol (GEP) - this value is also cached since it shouldn't change for the lifetime of this class                        if (virtualProtocolID == 0)                            virtualProtocolID = int.Parse(command.ExecuteScalar("SELECT ID FROM Protocol WHERE Acronym = 'VirtualInput'", MetadataSynchronizationTimeout).ToString());                         // Define SQL statement to query if parent device is already defined                        string parentDeviceIDSql = database.ParameterizedQueryString("SELECT ID FROM Device WHERE Acronym = {0}", "acronym");                         // Define SQL statement to insert new parent device record                        string insertParentDeviceSql = database.ParameterizedQueryString("INSERT INTO Device(NodeID, HistorianID, Acronym, Name, ProtocolID, IsConcentrator, Enabled) " +                            "VALUES ({0}, {1}, {2}, {3}, {4}, 1, 1)", "nodeID", "historianID", "acronym", "name", "protocolID");                         // Query the actual record ID based on the known run-time ID for this subscriber device                        object sourceID = command.ExecuteScalar(parentDeviceIDSql, MetadataSynchronizationTimeout, ParentDeviceAcronym);                         if (sourceID == null || sourceID == DBNull.Value)                        {                            // Get a historian ID, but exclude the STAT historian                            object randomHistorianID = command.ExecuteScalar($"SELECT ID FROM Historian WHERE Acronym <> 'STAT'", MetadataSynchronizationTimeout);                            command.ExecuteNonQuery(insertParentDeviceSql, MetadataSynchronizationTimeout, database.Guid(nodeID), randomHistorianID, ParentDeviceAcronym, ParentDeviceAcronym, virtualProtocolID);                            sourceID = command.ExecuteScalar(parentDeviceIDSql, MetadataSynchronizationTimeout, ParentDeviceAcronym);                        }                         int parentID = Convert.ToInt32(sourceID);                         // Validate that the subscriber device is marked as a concentrator (we are about to associate children devices with it)                        if (!command.ExecuteScalar($"SELECT IsConcentrator FROM Device WHERE ID = {parentID}", MetadataSynchronizationTimeout).ToString().ParseBoolean())                            command.ExecuteNonQuery($"UPDATE Device SET IsConcentrator = 1 WHERE ID = {parentID}", MetadataSynchronizationTimeout);                         // Get any historian associated with the subscriber device                        object historianID = command.ExecuteScalar($"SELECT HistorianID FROM Device WHERE ID = {parentID}", MetadataSynchronizationTimeout);                         // Ascertain total number of actions required for all metadata synchronization so some level feed back can be provided on progress                        initSyncProgress(metadata.Tables.Cast<DataTable>().Sum(dataTable => (long)dataTable.Rows.Count) + 3);                         // Prefix all children devices with the name of the parent since the same device names could appear in different connections (helps keep device names unique)                        string sourcePrefix = UseSourcePrefixNames ? ParentDeviceAcronym + "!" : "";                        Dictionary<string, int> deviceIDs = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);                        string deviceAcronym, signalTypeAcronym;                        int deviceID;                         // Check to see if data for the "DeviceDetail" table was included in the metadata                        if (metadata.Tables.Contains("DeviceDetail"))                        {                            DataTable deviceDetail = metadata.Tables["DeviceDetail"];                            List<Guid> uniqueIDs = new List<Guid>();                             // Define SQL statement to query if this device is already defined (this should always be based on the unique guid-based device ID)                            string deviceExistsSql = database.ParameterizedQueryString("SELECT COUNT(*) FROM Device WHERE UniqueID = {0}", "uniqueID");                             // Define SQL statement to insert new device record                            string insertDeviceSql = database.ParameterizedQueryString("INSERT INTO Device(NodeID, ParentID, HistorianID, Acronym, Name, ProtocolID, FramesPerSecond, AccessID, Longitude, Latitude, ContactList, IsConcentrator, Enabled) " +                                                                                       "VALUES ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, 0, 1)", "nodeID", "parentID", "historianID", "acronym", "name", "protocolID", "framesPerSecond", "accessID", "longitude", "latitude", "contactList"); 

Lines 1–500 of 981.

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