//******************************************************************************************************
// Program.cs - Gbtc
//
// Copyright © 2013, 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:
// ----------------------------------------------------------------------------------------------------
// 02/07/2013 - Stephen C. Wills
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Reflection;
using GSF;
using GSF.Configuration;
using GSF.Data;
using GSF.IO;
using GSF.Units;
using SerializationFormat = GSF.SerializationFormat;
namespace ConfigurationCacher
{
///
/// Configuration data source type enumeration.
///
public enum ConfigurationType
{
///
/// Configuration source is a database.
///
Database,
///
/// Configuration source is a webservice.
///
WebService,
///
/// Configuration source is a binary file.
///
BinaryFile,
///
/// Configuration source is a XML file.
///
XmlFile
}
public class Program
{
private static Guid s_nodeID;
private static ConfigurationType s_configurationType;
private static string s_connectionString;
private static string s_dataProviderString;
private static string s_cachedBinaryConfigurationFile;
private static string s_cachedXmlConfigurationFile;
private static int s_configurationBackups;
public static void Main(string[] args)
{
CategorizedSettingsElementCollection systemSettings = ConfigurationFile.Open(GetConfigurationFileName(args)).Settings["systemSettings"];
string cachePath = string.Format("{0}{1}ConfigurationCache{1}", FilePath.GetAbsolutePath(""), Path.DirectorySeparatorChar);
DataSet configuration;
systemSettings.Add("NodeID", Guid.NewGuid().ToString(), "Unique Node ID");
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("ConfigurationBackups", 5, "Defines the total number of older backup configurations to maintain.");
s_nodeID = systemSettings["NodeID"].ValueAs();
s_configurationType = systemSettings["ConfigurationType"].ValueAs();
s_connectionString = systemSettings["ConnectionString"].Value;
s_dataProviderString = systemSettings["DataProviderString"].Value;
s_cachedXmlConfigurationFile = FilePath.AddPathSuffix(cachePath) + systemSettings["CachedConfigurationFile"].Value;
s_cachedBinaryConfigurationFile = FilePath.AddPathSuffix(cachePath) + FilePath.GetFileNameWithoutExtension(s_cachedXmlConfigurationFile) + ".bin";
s_configurationBackups = systemSettings["ConfigurationBackups"].ValueAs(5);
configuration = GetConfigurationDataSet();
ExecuteConfigurationCache(configuration);
Console.WriteLine("Press 'Enter' to exit...");
Console.ReadLine();
}
private static string GetConfigurationFileName(string[] args)
{
string[] knownConfigurationFileNames = { "openPDC.exe.config", "openPG.exe.config", "openHistorian.exe.config", "SIEGate.exe.config" };
string absolutePath = "Configuration file";
// Search for the file name in the arguments to the program
foreach (string arg in args)
{
absolutePath = FilePath.GetAbsolutePath(arg);
if (File.Exists(absolutePath))
return absolutePath;
}
// Search for the file name in the list of known configuration files
foreach (string fileName in knownConfigurationFileNames)
{
absolutePath = FilePath.GetAbsolutePath(fileName);
if (File.Exists(absolutePath))
return absolutePath;
}
// Prompt the user for the file name
Console.WriteLine("Unable to find a configuration file. Please enter a file name:");
while (true)
{
absolutePath = FilePath.GetAbsolutePath(Console.ReadLine());
if (!File.Exists(absolutePath))
Console.WriteLine("Unable to find {0}. Please enter a file name:", FilePath.GetFileName(absolutePath));
else
break;
}
return absolutePath;
}
// Load system configuration data set
[SuppressMessage("Microsoft.Reliability", "CA2000")]
private static DataSet GetConfigurationDataSet()
{
DataSet configuration = null;
bool configException = false;
Ticks startTime = DateTime.UtcNow.Ticks;
Time elapsedTime;
string nodeIDQueryString = null;
switch (s_configurationType)
{
case ConfigurationType.Database:
// Attempt to load configuration from a database connection
IDbConnection connection = null;
Dictionary settings;
string assemblyName, connectionTypeName, adapterTypeName;
string setting;
Assembly assembly;
Type connectionType, adapterType;
DataTable entities, source, destination;
try
{
settings = s_dataProviderString.ParseKeyValuePairs();
assemblyName = settings["AssemblyName"].ToNonNullString();
connectionTypeName = settings["ConnectionType"].ToNonNullString();
adapterTypeName = settings["AdapterType"].ToNonNullString();
if (string.IsNullOrWhiteSpace(connectionTypeName))
throw new InvalidOperationException("Database connection type was not defined.");
if (string.IsNullOrWhiteSpace(adapterTypeName))
throw new InvalidOperationException("Database adapter type was not defined.");
assembly = Assembly.Load(new AssemblyName(assemblyName));
connectionType = assembly.GetType(connectionTypeName);
adapterType = assembly.GetType(adapterTypeName);
connection = (IDbConnection)Activator.CreateInstance(connectionType);
connection.ConnectionString = s_connectionString;
connection.Open();
configuration = new DataSet("Iaon");
// Load configuration entities defined in database
entities = connection.RetrieveData(adapterType, "SELECT * FROM ConfigurationEntity WHERE Enabled <> 0 ORDER BY LoadOrder");
entities.TableName = "ConfigurationEntity";
// Add configuration entities table to system configuration for reference
configuration.Tables.Add(entities.Copy());
// Get the node ID query string
if (settings.TryGetValue("Provider", out setting))
{
// Check if provider is for Access since it uses braces as Guid delimiters
if (setting.StartsWith("Microsoft.Jet.OLEDB", StringComparison.OrdinalIgnoreCase))
{
nodeIDQueryString = "{" + s_nodeID + "}";
// Make sure path to Access database is fully qualified
if (settings.TryGetValue("Data Source", out setting))
{
settings["Data Source"] = FilePath.GetAbsolutePath(setting);
s_connectionString = settings.JoinKeyValuePairs();
}
}
}
if (string.IsNullOrWhiteSpace(nodeIDQueryString))
nodeIDQueryString = "'" + s_nodeID + "'";
Ticks operationStartTime;
Time operationElapsedTime;
// Add each configuration entity to the system configuration
foreach (DataRow entityRow in entities.Rows)
{
// Load configuration entity data filtered by node ID
operationStartTime = DateTime.UtcNow.Ticks;
source = connection.RetrieveData(adapterType, string.Format("SELECT * FROM {0} WHERE NodeID={1}", entityRow["SourceName"], nodeIDQueryString));
operationElapsedTime = (DateTime.UtcNow.Ticks - operationStartTime).ToSeconds();
// Update table name as defined in configuration entity
source.TableName = entityRow["RuntimeName"].ToString();
DisplayStatusMessage("Loaded {0} row{1} from \"{2}\" in {3}...", UpdateType.Information, source.Rows.Count, source.Rows.Count == 1 ? "" : "s", source.TableName, operationElapsedTime.ToString(3));
operationStartTime = DateTime.UtcNow.Ticks;
// Clone data source
destination = source.Clone();
// Get destination column collection
DataColumnCollection columns = destination.Columns;
// Remove redundant node ID column
columns.Remove("NodeID");
// Pre-cache column index translation after removal of NodeID column to speed data copy
Dictionary columnIndex = new Dictionary();
foreach (DataColumn column in columns)
{
columnIndex[column.Ordinal] = source.Columns[column.ColumnName].Ordinal;
}
// Manually copy-in each row into table
foreach (DataRow sourceRow in source.Rows)
{
DataRow newRow = destination.NewRow();
// Copy each column of data in the current row
for (int x = 0; x < columns.Count; x++)
{
newRow[x] = sourceRow[columnIndex[x]];
}
// Add new row to destination table
destination.Rows.Add(newRow);
}
operationElapsedTime = (DateTime.UtcNow.Ticks - operationStartTime).ToSeconds();
// Add entity configuration data to system configuration
configuration.Tables.Add(destination);
DisplayStatusMessage("{0} configuration pre-cache completed in {1}.", UpdateType.Information, source.TableName, operationElapsedTime.ToString(3));
}
DisplayStatusMessage("Database configuration successfully loaded.", UpdateType.Information);
}
catch (Exception ex)
{
configException = true;
DisplayStatusMessage("Failed to load database configuration due to exception: {0}", UpdateType.Warning, ex.Message);
}
finally
{
if (connection != null)
connection.Dispose();
DisplayStatusMessage("Database configuration connection closed.", UpdateType.Information);
}
break;
case ConfigurationType.WebService:
// Attempt to load configuration from webservice based connection
WebRequest request = null;
Stream response = null;
try
{
DisplayStatusMessage("Webservice configuration connection opened.", UpdateType.Information);
configuration = new DataSet();
request = WebRequest.Create(s_connectionString);
response = request.GetResponse().GetResponseStream();
configuration.ReadXml(response);
DisplayStatusMessage("Webservice configuration successfully loaded.", UpdateType.Information);
}
catch (Exception ex)
{
configException = true;
DisplayStatusMessage("Failed to load webservice configuration due to exception: {0}", UpdateType.Warning, ex.Message);
}
finally
{
if (response != null)
response.Dispose();
DisplayStatusMessage("Webservice configuration connection closed.", UpdateType.Information);
}
break;
case ConfigurationType.BinaryFile:
// Attempt to load cached binary configuration file
try
{
DisplayStatusMessage("Loading binary based configuration from \"{0}\".", UpdateType.Information, s_connectionString);
configuration = Serialization.Deserialize(File.OpenRead(s_connectionString), SerializationFormat.Binary);
DisplayStatusMessage("Binary based configuration successfully loaded.", UpdateType.Information);
}
catch (Exception ex)
{
configException = true;
DisplayStatusMessage("Failed to load binary based configuration due to exception: {0}.", UpdateType.Alarm, ex.Message);
}
break;
case ConfigurationType.XmlFile:
// Attempt to load cached XML configuration file
try
{
DisplayStatusMessage("Loading XML based configuration from \"{0}\".", UpdateType.Information, s_connectionString);
configuration = new DataSet();
configuration.ReadXml(s_connectionString);
DisplayStatusMessage("XML based configuration successfully loaded.", UpdateType.Information);
}
catch (Exception ex)
{
configException = true;
DisplayStatusMessage("Failed to load XML based configuration due to exception: {0}.", UpdateType.Alarm, ex.Message);
}
break;
}
if (!configException)
{
elapsedTime = (DateTime.UtcNow.Ticks - startTime).ToSeconds();
DisplayStatusMessage("{0} configuration load process completed in {1}...", UpdateType.Information, s_configurationType, elapsedTime.ToString(3));
}
return configuration;
}
// Executes actual serialization of current configuration
private static void ExecuteConfigurationCache(DataSet configuration)
{
if ((object)configuration != null)
{
// Create backups of binary configurations
BackupConfiguration(ConfigurationType.BinaryFile, s_cachedBinaryConfigurationFile);
// Create backups of XML configurations
BackupConfiguration(ConfigurationType.XmlFile, s_cachedXmlConfigurationFile);
try
{
// Wait a moment for write lock in case binary file is open by another process
if (File.Exists(s_cachedBinaryConfigurationFile))
FilePath.WaitForWriteLock(s_cachedBinaryConfigurationFile);
// Cache binary serialized version of data set
using (FileStream configurationFileStream = File.OpenWrite(s_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);
}
// 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(s_cachedXmlConfigurationFile))
FilePath.WaitForWriteLock(s_cachedXmlConfigurationFile);
// Cache XML serialized version of data set
configuration.WriteXml(s_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);
}
}
}
private static void BackupConfiguration(ConfigurationType configType, string configurationFile)
{
try
{
// Create multiple backup configurations, if requested
for (int i = s_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);
}
try
{
// 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);
}
}
private static void DisplayStatusMessage(string message, UpdateType updateType, params object[] args)
{
TextWriter writer = (updateType == UpdateType.Alarm) ? Console.Error : Console.Out;
switch (updateType)
{
case UpdateType.Information:
Console.ForegroundColor = ConsoleColor.White;
break;
case UpdateType.Warning:
Console.ForegroundColor = ConsoleColor.Yellow;
break;
case UpdateType.Alarm:
Console.ForegroundColor = ConsoleColor.Red;
break;
}
writer.WriteLine(message, args);
}
}
}