//******************************************************************************************************
// ServiceHostBase.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:
// ----------------------------------------------------------------------------------------------------
// 08/20/2010 - J. Ritchie Carroll
// Generated original version of source code.
// 01/30/2011 - Pinal C. Patel
// Updated to subscribe to new ProcessException event of MultiDestinationExporter class.
// 04/21/2011 - Timothy M. Yardley / Erich A. Heine (UIUC)
// Implemented an optional, but enabled by default, non-broadcast method of directly routed
// measurement distribution as a system performance optimization.
// 12/20/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using GSF.Collections;
using GSF.Communication;
using GSF.Configuration;
using GSF.Console;
using GSF.Data;
using GSF.IO;
using GSF.Net.Security;
using GSF.Reflection;
using GSF.Security;
using GSF.ServiceProcess;
using GSF.Threading;
using GSF.TimeSeries.Adapters;
using GSF.TimeSeries.Configuration;
using GSF.TimeSeries.Reports;
using GSF.Units;
namespace GSF.TimeSeries
{
#region [ Enumerations ]
///
/// Configuration data source type enumeration.
///
public enum ConfigurationType
{
///
/// Configuration source is a database.
///
Database,
///
/// Configuration source is a web-service.
///
WebService,
///
/// Configuration source is a binary file.
///
BinaryFile,
///
/// Configuration source is a XML file.
///
XmlFile
}
#endregion
///
/// Represents the time-series framework service host.
///
public partial class ServiceHostBase : ServiceBase
{
#region [ Members ]
// Constants
private const int DefaultMinThreadPoolSize = 25;
private const int DefaultMaxThreadPoolSize = 2048;
private const int DefaultConfigurationBackups = 5;
internal event EventHandler> UpdatedStatus;
internal event EventHandler> LoggedException;
// Fields
private IaonSession m_iaonSession;
private string m_nodeIDQueryString;
private ConfigurationType m_configurationType;
private IConfigurationLoader m_configurationLoader;
private BinaryFileConfigurationLoader m_binaryCacheConfigurationLoader;
private XMLConfigurationLoader m_xmlCacheConfigurationLoader;
private string m_cachedXmlConfigurationFile;
private string m_cachedBinaryConfigurationFile;
private int m_configurationBackups;
private bool m_uniqueAdapterIDs;
private bool m_allowRemoteRestart;
private bool m_preferCachedConfiguration;
private MultipleDestinationExporter m_healthExporter;
private MultipleDestinationExporter m_statusExporter;
private ReportingProcessCollection m_reportingProcesses;
private ProcessQueue>> m_reloadConfigQueue;
private LongSynchronizedOperation m_configurationCacheOperation;
private volatile DataSet m_latestConfiguration;
private RunTimeLog m_runTimeLog;
private ServiceHelper m_serviceHelper;
private ServerBase m_remotingServer;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Creates a new .
///
public ServiceHostBase()
{
InitializeComponent();
ServiceName = "IaonHost";
}
///
/// Creates a new from specified parameters.
///
/// Service host .
public ServiceHostBase(IContainer container)
: this()
{
if (container != null)
container.Add(this);
}
#endregion
#region [ Properties ]
///
/// Gets the related remote console application name.
///
protected virtual string ConsoleApplicationName
{
get
{
return ServiceName + "Console.exe";
}
}
///
/// Gets access to the .
///
protected ServiceHelper ServiceHelper
{
get
{
return m_serviceHelper;
}
}
///
/// Gets reference to the based remoting server.
///
protected ServerBase RemotingServer
{
get
{
return m_remotingServer;
}
}
///
/// Gets reference to the .
///
protected AllAdaptersCollection AllAdapters
{
get
{
return m_iaonSession.AllAdapters;
}
}
///
/// Gets reference to the .
///
protected InputAdapterCollection InputAdapters
{
get
{
return m_iaonSession.InputAdapters;
}
}
///
/// Gets reference to the .
///
protected ActionAdapterCollection ActionAdapters
{
get
{
return m_iaonSession.ActionAdapters;
}
}
///
/// Gets reference to the .
///
protected OutputAdapterCollection OutputAdapters
{
get
{
return m_iaonSession.OutputAdapters;
}
}
///
/// Gets the current node ID.
///
protected Guid NodeID
{
get
{
return m_iaonSession.NodeID;
}
}
///
/// Gets the current node ID formatted for use in a SQL query string based on .
///
protected string NodeIDQueryString
{
get
{
return m_nodeIDQueryString;
}
}
///
/// Gets the currently loaded system configuration .
///
protected DataSet DataSource
{
get
{
return m_iaonSession.DataSource;
}
}
///
/// Gets the defined system .
///
protected ConfigurationType ConfigurationType
{
get
{
return m_configurationType;
}
}
#endregion
#region [ Methods ]
#region [ Service Event Handlers ]
///
/// Event handler for service starting operations.
///
/// Event source.
/// Event arguments containing command line arguments passed into service at startup.
///
/// Time-series framework uses this handler to load settings from configuration file as service is starting.
///
protected virtual void ServiceStartingHandler(object sender, EventArgs e)
{
// Define a run-time log
m_runTimeLog = new RunTimeLog();
m_runTimeLog.FileName = "RunTimeLog.txt";
m_runTimeLog.ProcessException += ProcessExceptionHandler;
m_runTimeLog.Initialize();
// Initialize Iaon session
m_iaonSession = new IaonSession();
m_iaonSession.StatusMessage += StatusMessageHandler;
m_iaonSession.ProcessException += ProcessExceptionHandler;
m_iaonSession.ConfigurationChanged += ConfigurationChangedHandler;
// Create a handler for unobserved task exceptions
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
// Make sure default service settings exist
ConfigurationFile configFile = ConfigurationFile.Current;
string cachePath = string.Format("{0}{1}ConfigurationCache{1}", FilePath.GetAbsolutePath(""), Path.DirectorySeparatorChar);
// System settings
CategorizedSettingsElementCollection systemSettings = configFile.Settings["systemSettings"];
systemSettings.Add("ConfigurationType", "Database", "Specifies type of configuration: Database, WebService, BinaryFile or XmlFile");
systemSettings.Add("ConnectionString", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=IaonHost.mdb", "Configuration database connection string");
systemSettings.Add("DataProviderString", "AssemblyName={System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089};ConnectionType=System.Data.OleDb.OleDbConnection;AdapterType=System.Data.OleDb.OleDbDataAdapter", "Configuration database ADO.NET data provider assembly type creation string");
systemSettings.Add("ConfigurationCachePath", cachePath, "Defines the path used to cache serialized configurations");
systemSettings.Add("CachedConfigurationFile", "SystemConfiguration.xml", "File name for last known good system configuration (only cached for a Database or WebService connection)");
systemSettings.Add("UniqueAdaptersIDs", "True", "Set to true if all runtime adapter ID's will be unique to allow for easier adapter specification");
systemSettings.Add("ProcessPriority", "High", "Sets desired process priority: Normal, AboveNormal, High, RealTime");
systemSettings.Add("AllowRemoteRestart", "True", "Controls ability to remotely restart the host service.");
systemSettings.Add("MinThreadPoolWorkerThreads", DefaultMinThreadPoolSize, "Defines the minimum number of allowed thread pool worker threads.");
systemSettings.Add("MaxThreadPoolWorkerThreads", DefaultMaxThreadPoolSize, "Defines the maximum number of allowed thread pool worker threads.");
systemSettings.Add("MinThreadPoolIOPortThreads", DefaultMinThreadPoolSize, "Defines the minimum number of allowed thread pool I/O completion port threads (used by socket layer).");
systemSettings.Add("MaxThreadPoolIOPortThreads", DefaultMaxThreadPoolSize, "Defines the maximum number of allowed thread pool I/O completion port threads (used by socket layer).");
systemSettings.Add("ConfigurationBackups", DefaultConfigurationBackups, "Defines the total number of older backup configurations to maintain.");
systemSettings.Add("PreferCachedConfiguration", "False", "Set to true to try the cached configuration first, before loading database configuration - typically used when cache is updated by external process.");
systemSettings.Add("LocalCertificate", string.Format("{0}.cer", ServiceName), "Path to the local certificate used by this server for authentication.");
systemSettings.Add("RemoteCertificatesPath", @"Certs\Remotes", "Path to the directory where remote certificates are stored.");
systemSettings.Add("DefaultCulture", "en-US", "Default culture to use for language, country/region and calendar formats.");
// Example connection settings
CategorizedSettingsElementCollection exampleSettings = configFile.Settings["exampleConnectionSettings"];
exampleSettings.Add("SqlServer.ConnectionString", "Data Source=serverName; Initial Catalog=databaseName; User ID=userName; Password=password", "Example SQL Server database connection string");
exampleSettings.Add("SqlServer.DataProviderString", "AssemblyName={System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089}; ConnectionType=System.Data.SqlClient.SqlConnection; AdapterType=System.Data.SqlClient.SqlDataAdapter", "Example SQL Server database .NET provider string");
exampleSettings.Add("MySQL.ConnectionString", "Server=serverName;Database=databaseName; Uid=root; Pwd=password; allow user variables = true;", "Example MySQL database connection string");
exampleSettings.Add("MySQL.DataProviderString", "AssemblyName={MySql.Data, Version=6.3.6.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d}; ConnectionType=MySql.Data.MySqlClient.MySqlConnection; AdapterType=MySql.Data.MySqlClient.MySqlDataAdapter", "Example MySQL database .NET provider string");
exampleSettings.Add("Oracle.ConnectionString", "Data Source=tnsName; User ID=schemaUserName; Password=schemaPassword", "Example Oracle database connection string");
exampleSettings.Add("Oracle.DataProviderString", "AssemblyName={Oracle.DataAccess, Version=2.112.2.0, Culture=neutral, PublicKeyToken=89b483f429c47342}; ConnectionType=Oracle.DataAccess.Client.OracleConnection; AdapterType=Oracle.DataAccess.Client.OracleDataAdapter", "Example Oracle database .NET provider string");
exampleSettings.Add("SQLite.ConnectionString", "Data Source=databaseName.db; Version=3; Foreign Keys=True; FailIfMissing=True", "Example SQLite database connection string");
exampleSettings.Add("SQLite.DataProviderString", "AssemblyName={System.Data.SQLite, Version=1.0.99.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139}; ConnectionType=System.Data.SQLite.SQLiteConnection; AdapterType=System.Data.SQLite.SQLiteDataAdapter", "Example SQLite database .NET provider string");
exampleSettings.Add("OleDB.ConnectionString", "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=databaseName.mdb", "Example Microsoft Access (via OleDb) database connection string");
exampleSettings.Add("OleDB.DataProviderString", "AssemblyName={System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089}; ConnectionType=System.Data.OleDb.OleDbConnection; AdapterType=System.Data.OleDb.OleDbDataAdapter", "Example OleDb database .NET provider string");
exampleSettings.Add("Odbc.ConnectionString", "Driver={SQL Server Native Client 10.0}; Server=serverName; Database=databaseName; Uid=userName; Pwd=password;", "Example ODBC database connection string");
exampleSettings.Add("Odbc.DataProviderString", "AssemblyName={System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089}; ConnectionType=System.Data.Odbc.OdbcConnection; AdapterType=System.Data.Odbc.OdbcDataAdapter", "Example ODBC database .NET provider string");
exampleSettings.Add("WebService.ConnectionString", "http://localhost/ConfigSource/SystemConfiguration.xml", "Example web service connection string");
exampleSettings.Add("XmlFile.ConnectionString", "SystemConfiguration.xml", "Example XML configuration file connection string");
// Attempt to set default culture
try
{
string defaultCulture = systemSettings["DefaultCulture"].ValueAs("en-US");
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.CreateSpecificCulture(defaultCulture); // Defaults for date formatting, etc.
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CreateSpecificCulture(defaultCulture); // Culture for resource strings, etc.
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to set default culture due to exception, defaulting to \"{1}\": {0}", UpdateType.Alarm, ex.Message, CultureInfo.CurrentCulture.Name.ToNonNullNorEmptyString("Undetermined"));
LogException(ex);
}
// Retrieve configuration cache directory as defined in the config file
cachePath = FilePath.GetAbsolutePath(systemSettings["ConfigurationCachePath"].Value);
// Make sure configuration cache directory exists
try
{
if (!Directory.Exists(cachePath))
Directory.CreateDirectory(cachePath);
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to create configuration cache directory due to exception: {0}", UpdateType.Alarm, ex.Message);
LogException(ex);
}
try
{
Directory.SetCurrentDirectory(FilePath.GetAbsolutePath(""));
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to set current directory to execution path due to exception: {0}", UpdateType.Alarm, ex.Message);
LogException(ex);
}
// Initialize system settings
m_configurationType = systemSettings["ConfigurationType"].ValueAs();
m_cachedXmlConfigurationFile = FilePath.AddPathSuffix(cachePath) + systemSettings["CachedConfigurationFile"].Value;
m_cachedBinaryConfigurationFile = FilePath.AddPathSuffix(cachePath) + FilePath.GetFileNameWithoutExtension(m_cachedXmlConfigurationFile) + ".bin";
m_configurationBackups = systemSettings["ConfigurationBackups"].ValueAs(DefaultConfigurationBackups);
m_uniqueAdapterIDs = systemSettings["UniqueAdaptersIDs"].ValueAsBoolean(true);
m_allowRemoteRestart = systemSettings["AllowRemoteRestart"].ValueAsBoolean(true);
m_preferCachedConfiguration = systemSettings["PreferCachedConfiguration"].ValueAsBoolean(false);
m_reloadConfigQueue = ProcessQueue>>.CreateSynchronousQueue(ExecuteReloadConfig, 500.0D, Timeout.Infinite, false, false);
m_reloadConfigQueue.ProcessException += m_iaonSession.ProcessExceptionHandler;
m_configurationCacheOperation = new LongSynchronizedOperation(ExecuteConfigurationCache)
{
IsBackground = true
};
// Setup default thread pool size
try
{
ThreadPool.SetMinThreads(systemSettings["MinThreadPoolWorkerThreads"].ValueAs(DefaultMinThreadPoolSize), systemSettings["MinThreadPoolIOPortThreads"].ValueAs(DefaultMinThreadPoolSize));
ThreadPool.SetMaxThreads(systemSettings["MaxThreadPoolWorkerThreads"].ValueAs(DefaultMaxThreadPoolSize), systemSettings["MaxThreadPoolIOPortThreads"].ValueAs(DefaultMaxThreadPoolSize));
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to set desired thread pool size due to exception: {0}", UpdateType.Alarm, ex.Message);
LogException(ex);
}
// Define guid with query string delimiters according to database needs
if (string.IsNullOrWhiteSpace(m_nodeIDQueryString))
m_nodeIDQueryString = "'" + m_iaonSession.NodeID + "'";
// Set up the configuration loader
switch (m_configurationType)
{
case ConfigurationType.Database:
m_configurationLoader = new DatabaseConfigurationLoader
{
ConnectionString = systemSettings["ConnectionString"].Value,
DataProviderString = systemSettings["DataProviderString"].Value,
NodeIDQueryString = m_nodeIDQueryString
};
break;
case ConfigurationType.WebService:
m_configurationLoader = new WebServiceConfigurationLoader
{
URI = systemSettings["ConnectionString"].Value
};
break;
case ConfigurationType.BinaryFile:
m_configurationLoader = new BinaryFileConfigurationLoader
{
FilePath = systemSettings["ConnectionString"].Value
};
break;
case ConfigurationType.XmlFile:
m_configurationLoader = new XMLConfigurationLoader
{
FilePath = systemSettings["ConnectionString"].Value
};
break;
}
m_binaryCacheConfigurationLoader = new BinaryFileConfigurationLoader
{
FilePath = m_cachedBinaryConfigurationFile
};
m_xmlCacheConfigurationLoader = new XMLConfigurationLoader
{
FilePath = m_cachedXmlConfigurationFile
};
m_configurationLoader.StatusMessage += (o, args) => DisplayStatusMessage(args.Argument, UpdateType.Information);
m_binaryCacheConfigurationLoader.StatusMessage += (o, args) => DisplayStatusMessage(args.Argument, UpdateType.Information);
m_xmlCacheConfigurationLoader.StatusMessage += (o, args) => DisplayStatusMessage(args.Argument, UpdateType.Information);
m_configurationLoader.ProcessException += ConfigurationLoader_ProcessException;
m_binaryCacheConfigurationLoader.ProcessException += ConfigurationLoader_ProcessException;
m_xmlCacheConfigurationLoader.ProcessException += ConfigurationLoader_ProcessException;
m_reloadConfigQueue.Start();
#if !MONO
try
{
// Attempt to assign desired process priority. Note that process will require SeIncreaseBasePriorityPrivilege or
// Administrative privileges to make this change
Process.GetCurrentProcess().PriorityClass = systemSettings["ProcessPriority"].ValueAs();
}
catch (Exception ex)
{
LogException(ex);
}
#endif
}
///
/// Event handler for service started operation.
///
/// Event source.
/// Event arguments.
///
/// Time-series framework uses this handler to handle initialization of system objects.
///
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 = 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,
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, string.Format("{0}Reporting", reportingProcess.ReportType));
// 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();
}
///
/// Event handler for service stopping operation.
///
/// Event source.
/// Event arguments.
///
/// Time-series framework uses this handler to un-wire events and dispose of system objects.
///
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;
}
private void UpdatedStatusHandler(object sender, EventArgs e)
{
if ((object)UpdatedStatus != null)
UpdatedStatus(sender, new EventArgs(e.Argument1, e.Argument2, e.Argument3));
}
private void LoggedExceptionHandler(object sender, EventArgs e)
{
if ((object)LoggedException != null)
LoggedException(sender, new EventArgs(e.Argument));
}
#endregion
#region [ System Initialization ]
private void GenerateLocalCertificate()
{
ConfigurationFile configurationFile;
CategorizedSettingsElementCollection remotingServer;
string certificatePath;
CertificateGenerator certificateGenerator;
X509Certificate2 certificate = null;
if (string.IsNullOrWhiteSpace(ServiceName))
throw new InvalidOperationException("EstablishServiceProperties must be overridden and ServiceName must be set");
try
{
configurationFile = ConfigurationFile.Current;
remotingServer = configurationFile.Settings["remotingServer"];
remotingServer.Add("CertificateFile", string.Format("{0}.cer", ServiceName), "Path to the local certificate used by this server for authentication.");
certificatePath = FilePath.GetAbsolutePath(remotingServer["CertificateFile"].Value);
certificateGenerator = new CertificateGenerator
{
Issuer = ServiceName,
CertificatePath = certificatePath
};
if (File.Exists(certificatePath))
certificate = new X509Certificate2(certificatePath);
if (!Equals(certificate, certificateGenerator.GenerateCertificate()))
EventLog.WriteEntry(ServiceName, string.Format("Created self-signed certificate for service: \"{0}\"", certificatePath), EventLogEntryType.Information, 0);
}
catch (Exception ex)
{
EventLog.WriteEntry(ServiceName, string.Format("{0}{3}{1}{3}{2}", ex.Message, ex.GetType().FullName, ex.StackTrace, Environment.NewLine), EventLogEntryType.Error, 0);
}
}
///
/// Initializes the service helper.
///
public void InitializeServiceHelper()
{
CategorizedSettingsElementCollection remotingServerSettings = ConfigurationFile.Current.Settings["remotingServer"];
m_serviceHelper = new ServiceHelper();
m_remotingServer = InitializeTlsServer();
if (remotingServerSettings.Cast().Any(element => element.Name.Equals("EnabledSslProtocols", StringComparison.OrdinalIgnoreCase) && !element.Value.Equals("None", StringComparison.OrdinalIgnoreCase)))
m_remotingServer = InitializeTlsServer();
else
m_remotingServer = InitializeTcpServer();
m_serviceHelper.ErrorLogger.ErrorLog.FileName = "ErrorLog.txt";
m_serviceHelper.ErrorLogger.ErrorLog.PersistSettings = true;
m_serviceHelper.ErrorLogger.ErrorLog.SettingsCategory = "ErrorLog";
m_serviceHelper.ErrorLogger.ErrorLog.Initialize();
m_serviceHelper.ErrorLogger.LogToEventLog = false;
m_serviceHelper.ErrorLogger.PersistSettings = true;
m_serviceHelper.ErrorLogger.SmtpServer = "";
m_serviceHelper.ErrorLogger.Initialize();
m_serviceHelper.ProcessScheduler.PersistSettings = true;
m_serviceHelper.ProcessScheduler.SettingsCategory = "ProcessScheduler";
m_serviceHelper.ProcessScheduler.Initialize();
m_serviceHelper.StatusLog.FileName = "StatusLog.txt";
m_serviceHelper.StatusLog.PersistSettings = true;
m_serviceHelper.StatusLog.SettingsCategory = "StatusLog";
m_serviceHelper.StatusLog.Initialize();
m_serviceHelper.ParentService = this;
m_serviceHelper.PersistSettings = true;
m_serviceHelper.RemotingServer = m_remotingServer;
m_serviceHelper.Initialize();
}
private TcpServer InitializeTcpServer()
{
TcpServer remotingServer;
remotingServer = new TcpServer();
remotingServer.ConfigurationString = "Port=8500";
remotingServer.IgnoreInvalidCredentials = true;
remotingServer.PayloadAware = true;
remotingServer.PersistSettings = true;
remotingServer.SettingsCategory = "RemotingServer";
remotingServer.Initialize();
return remotingServer;
}
private TlsServer InitializeTlsServer()
{
TlsServer remotingServer;
remotingServer = new TlsServer();
remotingServer.CertificateFile = "Internal.cer";
remotingServer.ConfigurationString = "Port=8500";
remotingServer.IgnoreInvalidCredentials = true;
remotingServer.PayloadAware = true;
remotingServer.PersistSettings = true;
remotingServer.SettingsCategory = "RemotingServer";
remotingServer.TrustedCertificatesPath = string.Format("Certs{0}Remotes", Path.DirectorySeparatorChar);
remotingServer.Initialize();
remotingServer.RemoteCertificateValidationCallback = (o, certificate, chain, errors) => true;
return remotingServer;
}
// Perform system initialization
private void InitializeSystem()
{
m_reloadConfigQueue.Add(Tuple.Create("Startup", new Action(success =>
{
// Attempt to load system configuration
if (success)
{
// Initialize and start all session adapters
m_iaonSession.Initialize();
DisplayStatusMessage("System initialization complete.", UpdateType.Information);
// If any settings have been added to configuration file, we go ahead and save them now
m_serviceHelper.SaveSettings(true);
ConfigurationFile.Current.Save();
}
else
{
DisplayStatusMessage("System initialization failed due to unavailable configuration.", UpdateType.Alarm);
}
// Log current thread pool size
int minWorkerThreads, minIOThreads, maxWorkerThreads, maxIOThreads;
ThreadPool.GetMinThreads(out minWorkerThreads, out minIOThreads);
ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxIOThreads);
DisplayStatusMessage("Thread pool size: minimum {0} worker {1} I/O, maximum {2} worker {3} I/O", UpdateType.Information, minWorkerThreads, minIOThreads, maxWorkerThreads, maxIOThreads);
})));
}
private void ExecuteReloadConfig(Tuple>[] items)
{
List requestTypes;
List distinctRequestsTypes;
DataSet configuration = null;
bool systemConfigurationLoaded = false;
requestTypes = items
.Select(tuple => tuple.Item1)
.ToList();
distinctRequestsTypes = requestTypes
.Distinct()
.OrderBy(type => requestTypes.LastIndexOf(type))
.ToList();
foreach (string type in distinctRequestsTypes)
{
string typeInner = type;
if (type == m_configurationType.ToString())
{
DisplayStatusMessage("Loading system configuration...", UpdateType.Information);
configuration = GetConfigurationDataSet();
// Update data source on all adapters in all collections
if ((object)configuration != null)
PropagateDataSource(configuration);
}
else if (type == "Augmented")
{
DisplayStatusMessage("Augmenting current system configuration...", UpdateType.Information);
configuration = AugmentConfigurationDataSet(DataSource);
// Update data source on all adapters in all collections
if ((object)configuration != null)
PropagateDataSource(configuration);
}
else if (type == "BinaryCache")
{
DisplayStatusMessage("Loading binary cached configuration...", UpdateType.Information);
configuration = GetBinaryCachedConfigurationDataSet();
// Update data source on all adapters in all collections
if ((object)configuration != null)
PropagateDataSource(configuration);
}
else if (type == "XmlCache")
{
DisplayStatusMessage("Loading XML cached configuration...", UpdateType.Information);
configuration = GetXMLCachedConfigurationDataSet();
// Update data source on all adapters in all collections
if ((object)configuration != null)
PropagateDataSource(configuration);
}
else if (type == "Startup")
{
systemConfigurationLoaded = LoadStartupConfiguration();
}
else
{
// No specific reload command was issued;
// load system configuration as normal
systemConfigurationLoaded = LoadSystemConfiguration();
}
foreach (Action callback in items.Where(tuple => tuple.Item1 == typeInner).Select(tuple => tuple.Item2))
{
try
{
callback(systemConfigurationLoaded || (object)configuration != null);
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to execute callback for ReloadConfig request of type {0}: {1}", UpdateType.Alarm, type, ex.Message);
LogException(ex);
}
}
// Spawn routing table calculation updates
m_iaonSession.RecalculateRoutingTables();
}
}
// Load the system configuration data set
private bool LoadStartupConfiguration()
{
DataSet configuration;
bool loadedFromCache;
// If cached configuration is not preferred,
// load system configuration normally
if (!m_preferCachedConfiguration)
return LoadSystemConfiguration();
// Cached configuration is preferred, so start by loading cached configuration
loadedFromCache = true;
configuration = GetBinaryCachedConfigurationDataSet();
if ((object)configuration == null)
configuration = GetXMLCachedConfigurationDataSet();
// If cached configuration failed to load,
// load from regular configuration data source after all
if ((object)configuration == null)
{
loadedFromCache = false;
configuration = GetConfigurationDataSet();
}
if ((object)configuration != null)
{
// Update data source on all adapters in all collections
if (PropagateDataSource(configuration))
{
// Cache the configuration if it wasn't already loaded from a cache
if (!loadedFromCache)
CacheCurrentConfiguration(configuration);
return true;
}
}
return false;
}
// Load the system configuration data set
private bool LoadSystemConfiguration()
{
DatabaseConfigurationLoader dbConfigurationLoader = m_configurationLoader as DatabaseConfigurationLoader;
DataSet configuration = null;
DataSet binaryCache = null;
DataSet xmlCache = null;
bool binaryCacheLoadAttempted = false;
bool xmlCacheLoadAttempted = false;
bool loadedFromCache = false;
try
{
// If the configuration is a database configuration, make sure
// to open the database before we start so we're not constantly
// opening and closing connections to the database
if ((object)dbConfigurationLoader != null)
dbConfigurationLoader.Open();
if (m_configurationLoader.CanAugment)
{
// Get the current running configuration
configuration = DataSource;
if ((object)configuration != null)
{
// Existing configuration is available so we attempt to augment that
DisplayStatusMessage("Attempting to augment existing configuration data set...", UpdateType.Information);
configuration = AugmentConfigurationDataSet(configuration.Copy());
}
else
{
// Attempt to load the binary cached configuration
binaryCache = GetBinaryCachedConfigurationDataSet();
binaryCacheLoadAttempted = true;
if ((object)binaryCache != null)
{
// Binary cached configuration is available so we attempt to augment that
DisplayStatusMessage("Attempting to augment binary cached configuration data set...", UpdateType.Information);
configuration = AugmentConfigurationDataSet(binaryCache.Copy());
}
else
{
// Attempt to load the XML cached configuration
xmlCache = GetXMLCachedConfigurationDataSet();
xmlCacheLoadAttempted = true;
if ((object)xmlCache != null)
{
// XML cached configuration is available so we attempt to augment that
DisplayStatusMessage("Attempting to augment XML cached configuration data set...", UpdateType.Information);
configuration = AugmentConfigurationDataSet(xmlCache.Copy());
}
}
}
}
if ((object)configuration == null)
{
// Either we cannot augment configuration or augmentation failed,
// so we attempt to load the whole configuration from the source
configuration = GetConfigurationDataSet();
}
if ((object)configuration == null)
{
// If all else fails, load configuration from the binary cache
loadedFromCache = true;
if (binaryCacheLoadAttempted)
configuration = binaryCache;
else
configuration = GetBinaryCachedConfigurationDataSet();
}
if ((object)configuration == null)
{
// If we even failed to load from the
// binary cache, load from the XML cache
if (xmlCacheLoadAttempted)
configuration = xmlCache;
else
configuration = GetXMLCachedConfigurationDataSet();
}
if ((object)configuration != null)
{
// Update data source on all adapters in all collections
if (PropagateDataSource(configuration))
{
// Cache the configuration if it wasn't already loaded from a cache
if (!loadedFromCache)
CacheCurrentConfiguration(configuration);
return true;
}
}
return false;
}
catch (Exception ex)
{
DisplayStatusMessage(ex.Message, UpdateType.Alarm);
LogException(ex);
// Most of the methods used above have a try-catch implemented to display helpful error messages
// and prevent this code from executing. This should only execute when using a database configuration
// as the primary configuration source and only if the system is unable to open a connection to the
// database. In that case, we simply attempt to load the cached configuration and use that instead
DisplayStatusMessage("Failed to open a connection to the database. Falling back on cached configuration...", UpdateType.Warning);
if ((object)configuration == null)
configuration = GetBinaryCachedConfigurationDataSet();
if ((object)configuration == null)
configuration = GetXMLCachedConfigurationDataSet();
if ((object)configuration != null)
return PropagateDataSource(configuration);
return false;
}
finally
{
if ((object)dbConfigurationLoader != null)
dbConfigurationLoader.Close();
}
}
private DataSet GetConfigurationDataSet()
{
try
{
return m_configurationLoader.Load();
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to load {0} configuration due to exception: {1}", UpdateType.Warning, m_configurationType, ex.Message);
LogException(ex);
return null;
}
}
private DataSet GetBinaryCachedConfigurationDataSet()
{
try
{
if (File.Exists(m_binaryCacheConfigurationLoader.FilePath))
return m_binaryCacheConfigurationLoader.Load();
return null;
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to load cached binary configuration due to exception: {0}", UpdateType.Alarm, ex.Message);
LogException(ex);
return null;
}
}
private DataSet GetXMLCachedConfigurationDataSet()
{
try
{
if (File.Exists(m_xmlCacheConfigurationLoader.FilePath))
return m_xmlCacheConfigurationLoader.Load();
return null;
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to load cached XML configuration due to exception: {0}", UpdateType.Alarm, ex.Message);
LogException(ex);
return null;
}
}
private DataSet AugmentConfigurationDataSet(DataSet configuration)
{
try
{
m_configurationLoader.Augment(configuration);
return configuration;
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to augment configuration due to exception: {0}", UpdateType.Warning, ex.Message);
LogException(ex);
return null;
}
}
private bool PropagateDataSource(DataSet dataSource)
{
try
{
m_iaonSession.DataSource = dataSource;
return true;
}
catch (Exception ex)
{
DisplayStatusMessage("Unable to propagate configuration data set to adapters due to exception: {0}", UpdateType.Alarm, ex.Message);
LogException(ex);
return false;
}
}
///
/// Caches the current system configuration.
///
/// Configuration .
///
/// This method allows caching of the current system configuration so it can be used if primary configuration source is unavailable.
///
protected virtual void CacheCurrentConfiguration(DataSet configuration)
{
m_latestConfiguration = configuration;
m_configurationCacheOperation.RunOnceAsync();
}
// Executes actual serialization of current configuration
private void ExecuteConfigurationCache()
{
DataSet configuration = m_latestConfiguration;
if ((object)configuration != null)
{
// Create backups of binary configurations
BackupConfiguration(ConfigurationType.BinaryFile, m_cachedBinaryConfigurationFile);
// Create backups of XML configurations
BackupConfiguration(ConfigurationType.XmlFile, m_cachedXmlConfigurationFile);
try
{
// Wait a moment for write lock in case binary file is open by another process
if (File.Exists(m_cachedBinaryConfigurationFile))
FilePath.WaitForWriteLock(m_cachedBinaryConfigurationFile);
// Cache binary serialized version of data set
using (FileStream configurationFileStream = File.OpenWrite(m_cachedBinaryConfigurationFile))
{
configuration.SerializeToStream(configurationFileStream);
}
DisplayStatusMessage("Successfully cached current configuration to binary.", UpdateType.Information);
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to cache last known configuration due to exception: {0}", UpdateType.Alarm, ex.Message);
LogException(ex);
}
// Serialize current data set to configuration files
try
{
// Wait a moment for write lock in case XML file is open by another process
if (File.Exists(m_cachedXmlConfigurationFile))
FilePath.WaitForWriteLock(m_cachedXmlConfigurationFile);
// Cache XML serialized version of data set
configuration.WriteXml(m_cachedXmlConfigurationFile, XmlWriteMode.WriteSchema);
DisplayStatusMessage("Successfully cached current configuration to XML.", UpdateType.Information);
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to cache last known configuration due to exception: {0}", UpdateType.Alarm, ex.Message);
LogException(ex);
}
}
}
private void BackupConfiguration(ConfigurationType configType, string configurationFile)
{
try
{
// Create multiple backup configurations, if requested
for (int i = m_configurationBackups; i > 0; i--)
{
string origConfigFile = configurationFile + ".backup" + (i == 1 ? "" : (i - 1).ToString());
if (File.Exists(origConfigFile))
{
string nextConfigFile = configurationFile + ".backup" + i;
if (File.Exists(nextConfigFile))
File.Delete(nextConfigFile);
File.Move(origConfigFile, nextConfigFile);
}
}
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to create extra backup {0} configurations due to exception: {1}", UpdateType.Warning, configType, ex.Message);
LogException(ex);
}
try
{
if (m_configurationBackups > 0)
{
// Back up current configuration file, if any
if (File.Exists(configurationFile))
{
string backupConfigFile = configurationFile + ".backup";
if (File.Exists(backupConfigFile))
File.Delete(backupConfigFile);
File.Move(configurationFile, backupConfigFile);
}
}
}
catch (Exception ex)
{
DisplayStatusMessage("Failed to backup last known cached {0} configuration due to exception: {1}", UpdateType.Warning, configType, ex.Message);
LogException(ex);
}
}
///
/// 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 override void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
if (disposing)
{
if ((object)components != null)
{
components.Dispose();
components = null;
}
if ((object)m_serviceHelper != null)
{
m_serviceHelper.Dispose();
m_serviceHelper = null;
}
if ((object)m_remotingServer != null)
{
m_remotingServer.Dispose();
m_remotingServer = null;
}
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
base.Dispose(disposing); // Call base class Dispose().
}
}
}
#endregion
#region [ Service Binding ]
internal void StartHostedService()
{
OnStart(Environment.CommandLine.Split(' '));
}
internal void StopHostedService()
{
OnStop();
}
internal void SendRequest(Guid clientID, string userInput)
{
ClientRequest request = ClientRequest.Parse(userInput);
if ((object)request != null)
{
ClientRequestHandler requestHandler = m_serviceHelper.FindClientRequestHandler(request.Command);
if ((object)requestHandler != null)
requestHandler.HandlerMethod(new ClientRequestInfo(new ClientInfo() { ClientID = clientID }, request));
else
DisplayStatusMessage(string.Format("Command \"{0}\" is not supported\r\n\r\n", request.Command), UpdateType.Alarm);
}
}
///
/// Handles service start event.
///
/// Service startup arguments, if any.
protected override void OnStart(string[] args)
{
GenerateLocalCertificate();
InitializeServiceHelper();
// Register service level event handlers
m_serviceHelper.ServiceStarting += ServiceStartingHandler;
m_serviceHelper.ServiceStarted += ServiceStartedHandler;
m_serviceHelper.ServiceStopping += ServiceStoppingHandler;
m_serviceHelper.UpdatedStatus += UpdatedStatusHandler;
m_serviceHelper.LoggedException += LoggedExceptionHandler;
if (m_serviceHelper.StatusLog != null)
m_serviceHelper.StatusLog.LogException += LogExceptionHandler;
if (m_serviceHelper.ErrorLogger != null && m_serviceHelper.ErrorLogger.ErrorLog != null)
m_serviceHelper.ErrorLogger.ErrorLog.LogException += LogExceptionHandler;
m_serviceHelper.OnStart(args);
}
///
/// Handles service stop event.
///
protected override void OnStop()
{
m_serviceHelper.OnStop();
}
///
/// Handles service pause event.
///
protected override void OnPause()
{
m_serviceHelper.OnPause();
}
///
/// Handles service continue event.
///
protected override void OnContinue()
{
m_serviceHelper.OnResume();
}
///
/// Handles service shut down event.
///
protected override void OnShutdown()
{
m_serviceHelper.OnShutdown();
}
#endregion
#region [ Primary Adapter Event Handlers ]
///
/// Event handler for reporting status messages.
///
/// Event source of the status message.
/// Event arguments containing the status message and its type to report.
///
/// The time-series framework uses this event to report adapter status messages (e.g., to a log file or console window).
///
private void StatusMessageHandler(object sender, EventArgs e)
{
DisplayStatusMessage(e.Argument1, e.Argument2);
}
///
/// Event handler for processing reported exceptions.
///
/// Event source of the exception.
/// Event arguments containing the exception to report.
///
/// The time-series framework uses this event to report exceptions.
///
private void ProcessExceptionHandler(object sender, EventArgs e)
{
LogException(e.Argument);
}
///
/// Event handler for processing notifications from adapters that configuration has changed.
///
/// Event source of the notification.
/// Event arguments, if any.
///
/// The time-series framework uses this event to report configuration changes.
///
private void ConfigurationChangedHandler(object sender, EventArgs e)
{
Action empty = success =>
{
};
m_reloadConfigQueue.Add(Tuple.Create("System", empty));
}
// Handle task scheduler exceptions
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
foreach (Exception ex in e.Exception.Flatten().InnerExceptions)
{
LogException(ex);
}
e.SetObserved();
}
///
/// Event handler for processing exceptions encountered while writing entries to a log file.
///
/// Event source of the exception.
/// Event arguments containing the exception to report.
protected virtual void LogExceptionHandler(object sender, EventArgs e)
{
DisplayStatusMessage("Log file exception: " + e.Argument.Message, UpdateType.Alarm);
}
///
/// Event handler for scheduled health monitor display.
///
/// Scheduled event name.
/// Scheduled event parameters.
protected virtual void HealthMonitorProcessHandler(string name, object[] parameters)
{
const string RequestCommand = "Health";
ClientRequestHandler requestHandler = m_serviceHelper.FindClientRequestHandler(RequestCommand);
if (requestHandler != null)
{
// We pretend to be a client and send a "Health" command to ourselves...
requestHandler.HandlerMethod(ClientHelper.PretendRequest(RequestCommand));
// We also export human readable health information to a text file for external display
m_healthExporter.ExportData(m_serviceHelper.PerformanceMonitor.Status);
}
}
///
/// Event handler for scheduled adapter status export.
///
/// Scheduled event name.
/// Scheduled event parameters.
protected virtual void StatusExportProcessHandler(string name, object[] parameters)
{
// Every thirty minutes we export a human readable service status to a text file for external display
m_statusExporter.ExportData(m_serviceHelper.Status);
}
///
/// Event handler for scheduled reporting services.
///
/// Scheduled event name.
/// Scheduled event parameters.
protected virtual void ReportingProcessHandler(string name, object[] parameters)
{
// Report processing schedule names end with "Reporting", remove suffix
// 123456789
if (name.EndsWith("Reporting", StringComparison.OrdinalIgnoreCase))
name = name.Substring(0, name.Length - 9);
// Lookup reporting process
IReportingProcess reportingProcess = m_reportingProcesses.FindReportType(name);
// Reporting process schedule is due, generate a new report
if ((object)reportingProcess != null)
{
reportingProcess.CleanReportLocation();
reportingProcess.GenerateReport(DateTime.UtcNow - TimeSpan.FromDays(1), true);
}
}
#endregion
#region [ Remote Client Request Handlers ]
///
/// Gets requested .
///
/// instance containing the client request.
/// Requested .
protected virtual IAdapterCollection GetRequestedCollection(ClientRequestInfo requestInfo)
{
if (requestInfo.Request.Arguments.Exists("A"))
return m_iaonSession.ActionAdapters;
if (requestInfo.Request.Arguments.Exists("O"))
return m_iaonSession.OutputAdapters;
return m_iaonSession.InputAdapters;
}
///
/// Gets requested .
///
/// instance containing the client request.
/// Requested .
protected virtual IAdapter GetRequestedAdapter(ClientRequestInfo requestInfo)
{
IAdapterCollection collection;
return GetRequestedAdapter(requestInfo, out collection);
}
///
/// Gets requested and its containing .
///
/// instance containing the client request.
/// Containing for .
/// Requested .
protected virtual IAdapter GetRequestedAdapter(ClientRequestInfo requestInfo, out IAdapterCollection collection)
{
IAdapter adapter;
string adapterID = requestInfo.Request.Arguments["OrderedArg1"];
collection = GetRequestedCollection(requestInfo);
if (!string.IsNullOrWhiteSpace(adapterID))
{
uint id;
adapterID = adapterID.Trim();
if (adapterID.IsAllNumbers() && uint.TryParse(adapterID, out id))
{
// Adapter ID is numeric, try numeric lookup by adapter ID in requested collection
if (collection.TryGetAdapterByID(id, out adapter))
return adapter;
// Try looking for ID in any collection if all runtime ID's are unique
if (m_uniqueAdapterIDs && m_iaonSession.AllAdapters.TryGetAnyAdapterByID(id, out adapter, out collection))
return adapter;
collection = GetRequestedCollection(requestInfo);
SendResponse(requestInfo, false, "Failed to find adapter with ID \"{0}\".", id);
}
else
{
// Adapter ID is alpha-numeric, try text-based lookup by adapter name in requested collection
if (collection.TryGetAdapterByName(adapterID, out adapter))
return adapter;
// Try looking for adapter name in any collection
if (m_iaonSession.AllAdapters.TryGetAnyAdapterByName(adapterID, out adapter, out collection))
return adapter;
collection = GetRequestedCollection(requestInfo);
SendResponse(requestInfo, false, "Failed to find adapter named \"{0}\".", adapterID);
}
}
return null;
}
///
/// Displays status of specified adapter or collection.
///
/// instance containing the client request.
protected virtual void ListRequestHandler(ClientRequestInfo requestInfo)
{
if (requestInfo.Request.Arguments.ContainsHelpRequest)
{
StringBuilder helpMessage = new StringBuilder();
helpMessage.Append("Displays status of specified adapter or collection.");
helpMessage.AppendLine();
helpMessage.AppendLine();
helpMessage.Append(" Usage:");
helpMessage.AppendLine();
helpMessage.Append(" List [ID] [Options]");
helpMessage.AppendLine();
helpMessage.AppendLine();
helpMessage.Append(" ID:".PadRight(20));
helpMessage.Append("ID of the adapter to display, or all adapters if not specified");
helpMessage.AppendLine();
helpMessage.Append(" Options:");
helpMessage.AppendLine();
helpMessage.Append(" -?".PadRight(20));
helpMessage.Append("Displays this help message");
helpMessage.AppendLine();
helpMessage.Append(" -I".PadRight(20));
helpMessage.Append("Enumerate input adapters (default)");
helpMessage.AppendLine();
helpMessage.Append(" -A".PadRight(20));
helpMessage.Append("Enumerate action adapters");
helpMessage.AppendLine();
helpMessage.Append(" -O".PadRight(20));
helpMessage.Append("Enumerate output adapters");
helpMessage.AppendLine();
DisplayResponseMessage(requestInfo, helpMessage.ToString());
}
else
{
StringBuilder adapterList = new StringBuilder();
IAdapterCollection collection = GetRequestedCollection(requestInfo);
IEnumerable listItems = collection;
bool idArgExists = requestInfo.Request.Arguments.Exists("OrderedArg1");
int enumeratedItems = 0;
adapterList.AppendFormat("System Uptime: {0}", m_serviceHelper.RemotingServer.RunTime.ToString());
adapterList.AppendLine();
adapterList.AppendLine();
if (idArgExists)
adapterList.AppendFormat(">> Selected adapter from {0}", collection.Name);
else
adapterList.AppendFormat(">> All defined adapters in {0} ({1} total)", collection.Name, collection.Count);
// Make a collection of one item for individual adapters
if (idArgExists)
{
IAdapter adapter = GetRequestedAdapter(requestInfo);
List singleItemList = new List();
if (adapter != null)
singleItemList.Add(adapter);
listItems = singleItemList;
}
adapterList.AppendLine();
adapterList.AppendLine();
adapterList.Append(" ID Name");
adapterList.AppendLine();
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
// 1 2 3 4 5 6 7 8
adapterList.Append("---------- --------------------------------------------------------------------");
// 123456789012345678901234567890123456789012345678901234567890123456789
// 1 2 3 4 5 6
adapterList.AppendLine();
foreach (IAdapter adapter in listItems)
{
adapterList.AppendFormat("{0} {1}", adapter.ID.ToString().CenterText(10), adapter.Name.TruncateRight(66));
adapterList.AppendLine();
adapterList.Append(" ");
adapterList.Append(adapter.GetShortStatus(68).TruncateRight(68));
// If a request was made to list a specific item, we request full status
if (idArgExists)
{
adapterList.AppendLine();
adapterList.AppendLine();
adapterList.Append(adapter.Status);
}
adapterList.AppendLine();
enumeratedItems++;
}
if (enumeratedItems > 0)
SendResponse(requestInfo, true, adapterList.ToString());
else
SendResponse(requestInfo, false, "No items were available enumerate.");
}
}
///
/// Starts specified adapter.
///
/// instance containing the client request.
protected virtual void StartRequestHandler(ClientRequestInfo requestInfo)
{
ActionRequestHandler(requestInfo, adapter => adapter.Start());
}
///
/// Stops specified adapter.
///
/// instance containing the client request.
protected virtual void StopRequestHandler(ClientRequestInfo requestInfo)
{
ActionRequestHandler(requestInfo, adapter => adapter.Stop());
}
///
/// Generic adapter request handler.
///
/// instance containing the client request.
/// Action to perform on .
protected virtual void ActionRequestHandler(ClientRequestInfo requestInfo, Action adapterAction)
{
string actionName = requestInfo.Request.Command.ToTitleCase();
if (requestInfo.Request.Arguments.ContainsHelpRequest)
{
StringBuilder helpMessage = new StringBuilder();
helpMessage.AppendFormat("Handles {0} command for specified adapter.", actionName);
helpMessage.AppendLine();
helpMessage.AppendLine();
helpMessage.Append(" Usage:");
helpMessage.AppendLine();
helpMessage.AppendFormat(" {0} ID [Options]", actionName);
helpMessage.AppendLine();
helpMessage.AppendLine();
helpMessage.Append(" ID:".PadRight(20));
helpMessage.Append("ID of the adapter to execute action on");
helpMessage.AppendLine();
helpMessage.Append(" Options:");
helpMessage.AppendLine();
helpMessage.Append(" -?".PadRight(20));
helpMessage.Append("Displays this help message");
helpMessage.AppendLine();
helpMessage.Append(" -I".PadRight(20));
helpMessage.AppendFormat("Perform {0} command on input adapters (default)", actionName);
helpMessage.AppendLine();
helpMessage.Append(" -A".PadRight(20));
helpMessage.AppendFormat("Perform {0} command on action adapters", actionName);
helpMessage.AppendLine();
helpMessage.Append(" -O".PadRight(20));
helpMessage.AppendFormat("Perform {0} command on output adapters", actionName);
helpMessage.AppendLine();
DisplayResponseMessage(requestInfo, helpMessage.ToString());
}
else
{
if (requestInfo.Request.Arguments.Exists("OrderedArg1"))
{
IAdapter adapter = GetRequestedAdapter(requestInfo);
if (adapter != null)
{
if (adapter.Initialized)
{
adapterAction(adapter);
SendResponse(requestInfo, true);
}
else
{
SendResponse(requestInfo, false, "Unable to {0} -- adapter is not initialized.", actionName);
}
}
}
else
SendResponse(requestInfo, false, "No ID was specified for \"{0}\" command.", actionName);
}
}
///
/// Invokes specified adapter command.
///
/// instance containing the client request.
protected virtual void InvokeRequestHandler(ClientRequestInfo requestInfo)
{
if (requestInfo.Request.Arguments.ContainsHelpRequest)
{
StringBuilder helpMessage = new StringBuilder();
helpMessage.Append("Invokes specified adapter command.");
helpMessage.AppendLine();
helpMessage.AppendLine();
helpMessage.Append(" Usage:");
helpMessage.AppendLine();
helpMessage.Append(" Invoke [ID] Command [Params] [Options]");
helpMessage.AppendLine();
helpMessage.AppendLine();
helpMessage.Append(" ID:".PadRight(20));
helpMessage.Append("ID of the adapter to execute command on");
helpMessage.AppendLine();
helpMessage.AppendLine(" If ID is not specified, action will be on collection itself.");
helpMessage.AppendLine();
helpMessage.Append(" Command:".PadRight(20));
helpMessage.Append("Name of the adapter command (i.e., method) to invoke");
helpMessage.AppendLine();
helpMessage.Append(" Params:".PadRight(20));
helpMessage.Append("Command parameters, if any, separated by spaces");
helpMessage.AppendLine();
helpMessage.Append(" Options:");
helpMessage.AppendLine();
helpMessage.Append(" -?".PadRight(20));
helpMessage.Append("Displays this help message");
helpMessage.AppendLine();
helpMessage.Append(" -I".PadRight(20));
helpMessage.Append("Invoke specified command on input adapter (default)");
helpMessage.AppendLine();
helpMessage.Append(" -A".PadRight(20));
helpMessage.Append("Invoke specified command on action adapter");
helpMessage.AppendLine();
helpMessage.Append(" -O".PadRight(20));
helpMessage.Append("Invoke specified command on output adapter");
helpMessage.AppendLine();
DisplayResponseMessage(requestInfo, helpMessage.ToString());
}
else
{
Thread invocationThread = new Thread(() =>
{
IAdapter adapter = null;
string command = null;
// See if specific ID for an adapter was requested
if (requestInfo.Request.Arguments.Exists("OrderedArg1"))
{
if (requestInfo.Request.Arguments.Exists("OrderedArg2"))
{
adapter = GetRequestedAdapter(requestInfo);
command = requestInfo.Request.Arguments["OrderedArg2"];
}
else
{
adapter = GetRequestedCollection(requestInfo);
command = requestInfo.Request.Arguments["OrderedArg1"];
}
}
else
SendResponse(requestInfo, false, "No command was specified to invoke.");
if (adapter != null)
{
try
{
// See if method exists with specified name using reflection
MethodInfo method = adapter.GetType().GetMethod(command, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase);
// Invoke method
if (method != null)
{
AdapterCommandAttribute commandAttribute;
// Make sure method is marked as invokable (i.e., AdapterCommandAttribute exists on method)
if (method.TryGetAttribute(out commandAttribute) && (!m_serviceHelper.SecureRemoteInteractions || commandAttribute.AllowedRoles.Any(role => Thread.CurrentPrincipal.IsInRole(role))))
{
ParameterInfo[] parameterInfo = method.GetParameters();
object returnValue = null;
bool success = true;
if (parameterInfo.Length == 0)
{
// Invoke parameterless adapter command
returnValue = method.Invoke(adapter, null);
}
else
{
int argCount = requestInfo.Request.Arguments.Count - 2;
int attachmentCount = requestInfo.Request.Attachments.Count;
// Create typed parameters for method and invoke
if (argCount + attachmentCount >= parameterInfo.Length)
{
// Attempt to convert command parameters to the method parameter types
object[] parameters = requestInfo.Request.Arguments.Skip(2)
.Select((arg, i) => arg.Value.ConvertToType