Inheritance: System.ComponentModel.Component, ISupportLifecycle, ISupportInitialize, IProvideStatus, IPersistSettings
        /// <summary>
        /// Initializes <see cref="FileExporter"/>.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            Dictionary<string, string> settings = Settings;
            const string errorMessage = "{0} is missing from Settings - Example: exportInterval=5; useReferenceAngle=True; referenceAngleMeasurement=DEVARCHIVE:6; companyTagPrefix=TVA; useNumericQuality=True; inputMeasurementKeys={{FILTER ActiveMeasurements WHERE Device='SHELBY' AND SignalType='FREQ'}}";
            string setting;
            double seconds;

            // Load required parameters
            if (!settings.TryGetValue("exportInterval", out setting) || !double.TryParse(setting, out seconds))
                throw new ArgumentException(string.Format(errorMessage, "exportInterval"));

            m_exportInterval = (int)(seconds * 1000.0D);
            m_lastPublicationTime = 0;

            if (m_exportInterval <= 0)
                throw new ArgumentException("exportInterval should not be 0 - Example: exportInterval=5.5");

            if (InputMeasurementKeys == null || InputMeasurementKeys.Length == 0)
                throw new InvalidOperationException("There are no input measurements defined. You must define \"inputMeasurementKeys\" to define which measurements to export.");

            if (!settings.TryGetValue("useReferenceAngle", out setting))
                throw new ArgumentException(string.Format(errorMessage, "useReferenceAngle"));

            m_useReferenceAngle = setting.ParseBoolean();

            if (m_useReferenceAngle)
            {
                // Reference angle measurement has to be defined if using reference angle
                if (!settings.TryGetValue("referenceAngleMeasurement", out setting))
                    throw new ArgumentException(string.Format(errorMessage, "referenceAngleMeasurement"));

                m_referenceAngleKey = MeasurementKey.Parse(setting);

                // Make sure reference angle is part of input measurement keys collection
                if (!InputMeasurementKeys.Contains(m_referenceAngleKey))
                    InputMeasurementKeys = InputMeasurementKeys.Concat(new[] { m_referenceAngleKey }).ToArray();

                // Make sure reference angle key is actually an angle measurement
                SignalType signalType = InputMeasurementKeyTypes[InputMeasurementKeys.IndexOf(key => key == m_referenceAngleKey)];

                if (signalType != SignalType.IPHA && signalType != SignalType.VPHA)
                    throw new InvalidOperationException($"Specified reference angle measurement key is a {signalType.GetFormattedName()} signal, not a phase angle.");
            }

            // Load optional parameters
            if (settings.TryGetValue("companyTagPrefix", out setting))
                m_companyTagPrefix = setting.ToUpper().Trim();
            else
                m_companyTagPrefix = null;

            if (settings.TryGetValue("useNumericQuality", out setting))
                m_useNumericQuality = setting.ParseBoolean();
            else
                m_useNumericQuality = false;

            // Suffix company tag prefix with an underscore if defined
            if (!string.IsNullOrWhiteSpace(m_companyTagPrefix))
                m_companyTagPrefix = m_companyTagPrefix.EnsureEnd('_');

            // Define a default export location - user can override and add multiple locations in config later...
            m_dataExporter = new MultipleDestinationExporter(ConfigurationSection, m_exportInterval);
            m_dataExporter.StatusMessage += m_dataExporter_StatusMessage;
            m_dataExporter.ProcessException += m_dataExporter_ProcessException;
            m_dataExporter.Initialize(new[] { new ExportDestination(FilePath.GetAbsolutePath(ConfigurationSection + ".txt"), false) });

            // Create new measurement tag name dictionary
            m_measurementTags = new ConcurrentDictionary<MeasurementKey, string>();
            string pointID = "undefined";

            // Lookup point tag name for input measurement in the ActiveMeasurements table
            foreach (MeasurementKey key in InputMeasurementKeys)
            {
                try
                {
                    // Get measurement key as a string
                    pointID = key.ToString();

                    // Lookup measurement key in active measurements table
                    DataRow row = DataSource.Tables["ActiveMeasurements"].Select($"ID='{pointID}'")[0];

                    // Remove invalid symbols that may be in tag name
                    string pointTag = row["PointTag"].ToNonNullString(pointID).Replace('-', '_').Replace(':', '_').ToUpper();

                    // Prefix point tag with company prefix if defined
                    if (!string.IsNullOrWhiteSpace(m_companyTagPrefix) && !pointTag.StartsWith(m_companyTagPrefix))
                        pointTag = m_companyTagPrefix + pointTag;

                    m_measurementTags.TryAdd(key, pointTag);
                }
                catch (Exception ex)
                {
                    OnProcessException(MessageLevel.Warning, new InvalidOperationException($"Failed to lookup point tag for measurement [{pointID}] due to exception: {ex.Message}"));
                }
            }

            // We enable tracking of latest measurements so we can use these values if points are missing - since we are using
            // latest measurement tracking, we sort all incoming points even though most of them will be thrown out...
            TrackLatestMeasurements = true;
        }
        /// <summary>
        /// Event handler for service stopping operation.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event arguments.</param>
        /// <remarks>
        /// Time-series framework uses this handler to un-wire events and dispose of system objects.
        /// </remarks>
        protected virtual void ServiceStoppingHandler(object sender, EventArgs e)
        {
            // Dispose system health exporter
            if ((object)m_healthExporter != null)
            {
                m_healthExporter.Enabled = false;
                m_serviceHelper.ServiceComponents.Remove(m_healthExporter);
                m_healthExporter.Dispose();
                m_healthExporter.StatusMessage -= m_iaonSession.StatusMessageHandler;
                m_healthExporter.ProcessException -= m_iaonSession.ProcessExceptionHandler;
                m_healthExporter = null;
            }

            // Dispose system status exporter
            if ((object)m_statusExporter != null)
            {
                m_statusExporter.Enabled = false;
                m_serviceHelper.ServiceComponents.Remove(m_statusExporter);
                m_statusExporter.Dispose();
                m_statusExporter.StatusMessage -= m_iaonSession.StatusMessageHandler;
                m_statusExporter.ProcessException -= m_iaonSession.ProcessExceptionHandler;
                m_statusExporter = null;
            }

            // Dispose reload config queue
            if ((object)m_reloadConfigQueue != null)
            {
                m_reloadConfigQueue.ProcessException -= m_iaonSession.ProcessExceptionHandler;
                m_reloadConfigQueue.Dispose();
                m_reloadConfigQueue = null;
            }

            // Dispose reporting processes
            if ((object)m_reportingProcesses != null)
            {
                m_serviceHelper.ServiceComponents.Remove(m_reportingProcesses);
                m_reportingProcesses = null;
            }

            // Dispose Iaon session
            if ((object)m_iaonSession != null)
            {
                m_serviceHelper.ServiceComponents.Remove(m_iaonSession.InputAdapters);
                m_serviceHelper.ServiceComponents.Remove(m_iaonSession.ActionAdapters);
                m_serviceHelper.ServiceComponents.Remove(m_iaonSession.OutputAdapters);

                m_iaonSession.Dispose();
                m_iaonSession.StatusMessage -= StatusMessageHandler;
                m_iaonSession.ProcessException -= ProcessExceptionHandler;
                m_iaonSession.ConfigurationChanged -= ConfigurationChangedHandler;
                m_iaonSession = null;
            }

            // Dispose of run-time log
            if ((object)m_runTimeLog != null)
            {
                m_serviceHelper.ServiceComponents.Remove(m_runTimeLog);
                m_runTimeLog.ProcessException -= ProcessExceptionHandler;
                m_runTimeLog.Dispose();
                m_runTimeLog = null;
            }

            m_serviceHelper.ServiceStarting -= ServiceStartingHandler;
            m_serviceHelper.ServiceStarted -= ServiceStartedHandler;
            m_serviceHelper.ServiceStopping -= ServiceStoppingHandler;
            m_serviceHelper.UpdatedStatus -= UpdatedStatusHandler;
            m_serviceHelper.LoggedException -= LoggedExceptionHandler;

            if ((object)m_serviceHelper.StatusLog != null)
            {
                m_serviceHelper.StatusLog.Flush();
                m_serviceHelper.StatusLog.LogException -= LogExceptionHandler;
            }

            if ((object)m_serviceHelper.ErrorLogger != null && (object)m_serviceHelper.ErrorLogger.ErrorLog != null)
            {
                m_serviceHelper.ErrorLogger.ErrorLog.Flush();
                m_serviceHelper.ErrorLogger.ErrorLog.LogException -= LogExceptionHandler;
            }

            // Detach from handler for unobserved task exceptions
            TaskScheduler.UnobservedTaskException -= TaskScheduler_UnobservedTaskException;

            ShutdownHandler.InitiateSafeShutdown();
        }
        /// <summary>
        /// Releases the unmanaged resources used by the <see cref="FileExporter"/> 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)
                    {
                        if (m_dataExporter != null)
                        {
                            m_dataExporter.SaveSettings();
                            m_dataExporter.StatusMessage -= m_dataExporter_StatusMessage;
                            m_dataExporter.ProcessException -= m_dataExporter_ProcessException;
                            m_dataExporter.Dispose();
                        }

                        m_dataExporter = null;
                    }
                }
                finally
                {
                    m_disposed = true;          // Prevent duplicate dispose.
                    base.Dispose(disposing);    // Call base class Dispose().
                }
            }
        }
        /// <summary>
        /// Event handler for service started operation.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event arguments.</param>
        /// <remarks>
        /// Time-series framework uses this handler to handle initialization of system objects.
        /// </remarks>
        protected virtual void ServiceStartedHandler(object sender, EventArgs e)
        {
            // Define a line of asterisks for emphasis
            string stars = new string('*', 79);

            // Get current process memory usage
            long processMemory = GSF.Common.GetProcessMemory();

            // Log startup information
            m_serviceHelper.UpdateStatus(
                UpdateType.Information,
                "\r\n\r\n{0}\r\n\r\n" +
                "Node {{{1}}} Initializing\r\n\r\n" +
                "     System Time: {2} UTC\r\n\r\n" +
                "    Current Path: {3}\r\n\r\n" +
                "    Machine Name: {4}\r\n\r\n" +
                "      OS Version: {5}\r\n\r\n" +
                "    Product Name: {6}\r\n\r\n" +
                "  Working Memory: {7}\r\n\r\n" +
                "  Execution Mode: {8}-bit\r\n\r\n" +
                "      Processors: {9}\r\n\r\n" +
                "  GC Server Mode: {10}\r\n\r\n" +
                " GC Latency Mode: {11}\r\n\r\n" +
                " Process Account: {12}\\{13}\r\n\r\n" +
                "{14}\r\n",
                stars,
                m_iaonSession.NodeID,
                DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff"),
                FilePath.TrimFileName(FilePath.RemovePathSuffix(FilePath.GetAbsolutePath("")), 61),
                Environment.MachineName,
                Environment.OSVersion.VersionString,
                GSF.Common.GetOSProductName(),
                processMemory > 0 ? SI2.ToScaledString(processMemory, 4, "B", SI2.IECSymbols) : "Undetermined",
                IntPtr.Size * 8,
                Environment.ProcessorCount,
                GCSettings.IsServerGC,
                GCSettings.LatencyMode,
                Environment.UserDomainName,
                Environment.UserName,
                stars);

            // Add run-time log as a service component
            m_serviceHelper.ServiceComponents.Add(m_runTimeLog);

            // Create health exporter
            m_healthExporter = new MultipleDestinationExporter("HealthExporter", Timeout.Infinite);
            m_healthExporter.Initialize(new[] { new ExportDestination(FilePath.GetAbsolutePath("Health.txt"), false) });
            m_healthExporter.StatusMessage += m_iaonSession.StatusMessageHandler;
            m_healthExporter.ProcessException += m_iaonSession.ProcessExceptionHandler;
            m_serviceHelper.ServiceComponents.Add(m_healthExporter);

            // Create status exporter
            m_statusExporter = new MultipleDestinationExporter("StatusExporter", Timeout.Infinite);
            m_statusExporter.Initialize(new[] { new ExportDestination(FilePath.GetAbsolutePath("Status.txt"), false) });
            m_statusExporter.StatusMessage += m_iaonSession.StatusMessageHandler;
            m_statusExporter.ProcessException += m_iaonSession.ProcessExceptionHandler;
            m_serviceHelper.ServiceComponents.Add(m_statusExporter);

            // Create reporting processes
            m_reportingProcesses = ReportingProcessCollection.LoadImplementations(ex =>
            {
                DisplayStatusMessage("Failed to load reporting process: {0}", UpdateType.Warning, ex.Message);
                LogException(ex);
            });
            m_serviceHelper.ServiceComponents.Add(m_reportingProcesses);

            // Define scheduled service processes
            m_serviceHelper.AddScheduledProcess(HealthMonitorProcessHandler, "HealthMonitor", "* * * * *");    // Every minute
            m_serviceHelper.AddScheduledProcess(StatusExportProcessHandler, "StatusExport", "*/30 * * * *");   // Every 30 minutes

            // Define processes for each report (initially unscheduled)
            foreach (IReportingProcess reportingProcess in m_reportingProcesses)
                m_serviceHelper.AddProcess(ReportingProcessHandler, $"{reportingProcess.ReportType}Reporting");

            // Add key Iaon collections as service components
            m_serviceHelper.ServiceComponents.Add(m_iaonSession.InputAdapters);
            m_serviceHelper.ServiceComponents.Add(m_iaonSession.ActionAdapters);
            m_serviceHelper.ServiceComponents.Add(m_iaonSession.OutputAdapters);

            // Define remote client requests (i.e., console commands)
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("List", "Displays status for specified adapter or collection", ListRequestHandler, new[] { "ls", "dir" }));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("Connect", "Connects (or starts) specified adapter", StartRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("Disconnect", "Disconnects (or stops) specified adapter", StopRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("Invoke", "Invokes a command for specified adapter", InvokeRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("ListCommands", "Displays possible commands for specified adapter", ListCommandsRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("Initialize", "Initializes specified adapter or collection", InitializeRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("ReloadConfig", "Manually reloads the system configuration", ReloadConfigRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("UpdateConfigFile", "Updates an option in the configuration file", UpdateConfigFileRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("Authenticate", "Authenticates network shares for health and status exports", AuthenticateRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("Restart", "Attempts to restart the host service", RestartServiceHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("RefreshRoutes", "Spawns request to recalculate routing tables", RefreshRoutesRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("TemporalSupport", "Determines if any adapters support temporal processing", TemporalSupportRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("ListReports", "Lists the available reports that have been generated", ListReportsRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("GetReport", "Gets a previously generated report for the specified date", GetReportRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("GetReportStatus", "Gets a reporting process status", GetReportStatusRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("GenerateReport", "Generates a report for the specified date", GenerateReportRequestHandler));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("ReportingConfig", "Displays or modifies the configuration of the reporting process", ReportingConfigRequestHandler, false));
            m_serviceHelper.ClientRequestHandlers.Add(new ClientRequestHandler("LogEvent", "Logs remote event log entries.", LogEventRequestHandler, false));

            // Start system initialization on an independent thread so that service responds in a timely fashion...
            InitializeSystem();
        }