using Edge.Core.Database; using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Linq; using System.Reflection; using RestSharp; using Newtonsoft.Json; using System.Threading.Tasks; using System.Xml; using System.Threading; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using System.Xml.Serialization; namespace Edge.Core.Configuration { public class Configurator { public event Action OnReadConfigInfo; private static Configurator instance = new Configurator(); private Timer timelyCheckRemoteConfig; //private List> registeredChangeCallbacks = new List>(); /// /// fired once the configuration file(settings.xml) changed. /// public event EventHandler OnConfigFileChanged; private IConfigurationRoot configurationRoot = null; public ILogger logger = NullLogger.Instance; private IConfigurationBuilder builder; public async Task LoadAsync() { try { //var ass = Assembly.GetExecutingAssembly(); this.builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("noExistsNow.json", optional: true, reloadOnChange: true) .AddXmlFile("settings.xml", optional: false, reloadOnChange: true); this.configurationRoot = this.builder.Build(); } catch (FileNotFoundException fnf) { Console.WriteLine(fnf); logger.LogError("Could not find local settings.xml"); this.OnReadConfigInfo?.Invoke("Could not find local settings.xml"); return false; } this.MetaConfiguration = this.RebindMetaFromFile(); string enableAndRetrieveConfigFromConfigServiceUrl = this.MetaConfiguration.Parameter?.FirstOrDefault(p => p.Name == "enableAndRetrieveConfigFromConfigServiceUrl")?.Value; if (string.IsNullOrEmpty(enableAndRetrieveConfigFromConfigServiceUrl)) { /* read config from local file */ //logger.LogInformation("Use Local Config."); //this.OnReadConfigInfo?.Invoke("Use Local Config..."); return this.LoadFromLocalFile(); } else { /* read config from remote */ logger.LogInformation("Use Remote Config against ConfigService url: " + enableAndRetrieveConfigFromConfigServiceUrl ?? ""); this.OnReadConfigInfo?.Invoke("Use Remote Config: " + enableAndRetrieveConfigFromConfigServiceUrl ?? ""); var loadResult = await this.LoadFromRemote(enableAndRetrieveConfigFromConfigServiceUrl); if (!loadResult) return false; // enable auto polling. if (this.timelyCheckRemoteConfig != null) this.timelyCheckRemoteConfig.Dispose(); this.timelyCheckRemoteConfig = new Timer(async (o) => { await this.LoadFromRemote(enableAndRetrieveConfigFromConfigServiceUrl); }, null, 20000, 20000); return true; } } private bool LoadFromLocalFile() { this.configurationRoot.GetReloadToken().RegisterChangeCallback((o) => { this.MetaConfiguration = this.RebindMetaFromFile(); this.NozzleExtraInfoConfiguration = this.RebindNozzleProductFromFile(); this.DeviceProcessorConfiguration = this.RebindDeviceProcessorFromFile(); this.OnConfigFileChanged?.Invoke(this, null); this.configurationRoot = builder.Build(); this.LoadFromLocalFile(); //this.registeredChangeCallbacks.ForEach(c => // // need improve, here just send a notification of the change // // but no specific processor. // c.Invoke(null)); }, null); this.MetaConfiguration = this.RebindMetaFromFile(); this.NozzleExtraInfoConfiguration = this.RebindNozzleProductFromFile(); this.DeviceProcessorConfiguration = this.RebindDeviceProcessorFromFile(); return true; } /// /// Read NozzleProduct and Processors configs from remote. /// /// /// private async Task LoadFromRemote(string enableAndRetrieveConfigFromConfigServiceUrl) { try { var newReadNozzleProductConfig = await this.ReadRemoteNozzleProductConfig( enableAndRetrieveConfigFromConfigServiceUrl, "LiteFccCoreSettings_NozzleProduct"); if (newReadNozzleProductConfig != null) if (!newReadNozzleProductConfig.Equals(this.NozzleExtraInfoConfiguration)) { logger.LogInformation("Remote Nozzle product config changed to:" + Environment.NewLine + this.ConvertObjectToString(newReadNozzleProductConfig) ?? "" + " will fire ChangeCallbacks..."); this.OnReadConfigInfo?.Invoke("Remote Nozzle Product Config changed(read mapping count: " + newReadNozzleProductConfig.Mapping.Count + ")..."); this.NozzleExtraInfoConfiguration = newReadNozzleProductConfig; this.OnConfigFileChanged?.Invoke(this, null); //this.registeredChangeCallbacks.ForEach(c => c.Invoke(this.NozzleProductConfiguration)); } var newReadProcessorConfig = await this.ReadRemoteProcessorsConfig( enableAndRetrieveConfigFromConfigServiceUrl, "LiteFccCoreSettings_Processor"); if (newReadProcessorConfig != null) if (!newReadProcessorConfig.Equals(this.DeviceProcessorConfiguration)) { logger.LogInformation("Remote processors config changed to:" + Environment.NewLine + this.ConvertObjectToString(newReadProcessorConfig) ?? "" + " will fire ChangeCallbacks..."); this.OnReadConfigInfo?.Invoke("Remote Processor Config changed(read processor count: " + newReadProcessorConfig.Processor.Count + ")..."); this.DeviceProcessorConfiguration = newReadProcessorConfig; this.OnConfigFileChanged?.Invoke(this, null); //this.registeredChangeCallbacks.ForEach(c => c.Invoke(this.DeviceProcessorConfiguration)); } return true; } catch (Exception exx) { logger.LogError("Configurator LoadFromRemote exceptioned: " + exx); this.OnReadConfigInfo?.Invoke("Read Remote Config error..."); return false; } } private string ConvertObjectToString(T config) where T : class { var loggingSerializer = new XmlSerializer(typeof(T)); MemoryStream ms = new MemoryStream(); loggingSerializer.Serialize(ms, config); ms.Position = 0; return new StreamReader(ms).ReadToEnd(); } /// /// private DeviceProcessorConfiguration RebindDeviceProcessorFromFile() { var deviceProcessorConfiguration = new DeviceProcessorConfiguration(); this.configurationRoot.GetSection("deviceProcessor").Bind(deviceProcessorConfiguration); return deviceProcessorConfiguration; } /// /// private NozzleExtraInfoConfiguration RebindNozzleProductFromFile() { var nozzleProductConfigurationFull = new NozzleExtraInfoConfiguration(); this.configurationRoot.GetSection("nozzleProduct").Bind(nozzleProductConfigurationFull); return nozzleProductConfigurationFull; } /// /// private MetaConfiguration RebindMetaFromFile() { var metaConfigurationFull = new MetaConfiguration(); configurationRoot.GetSection("meta").Bind(metaConfigurationFull); return metaConfigurationFull; } /// /// It's the necessary config, must be loaded from remote, otherwise exception. /// /// /// /// private async Task ReadRemoteProcessorsConfig( string configServiceBaseUrl, string settingsGroupName) { RestClient restClient = new RestClient(configServiceBaseUrl); RestRequest configDescRequest = new RestRequest("api/Config/description", Method.Get); configDescRequest.AddHeader("Content-Type", "application/json"); configDescRequest.AddQueryParameter("configGroupName", settingsGroupName); configDescRequest.AddQueryParameter("configOwnerIds", this.MetaConfiguration.Parameter.First(p => p.Name == "serialNumber").Value); configDescRequest.RequestFormat = DataFormat.Json; var configDescResponse = await restClient.ExecuteAsync(configDescRequest); if (configDescResponse.StatusCode != System.Net.HttpStatusCode.OK) { logger.LogInformation("Remote config service(api/Config/description) returned with Status code: " + configDescResponse.StatusCode); throw new ArgumentException("Remote config service(api/Config/description) returned with Status code: " + configDescResponse.StatusCode); } var configDescriptions = JsonConvert.DeserializeObject(configDescResponse.Content); var deviceProcessorConfiguration = new DeviceProcessorConfiguration() { Processor = new List() }; foreach (var configDesc in configDescriptions.Where(d => d.Enabled)) { var configRequest = new RestRequest("api/Config/", Method.Get); configRequest.AddHeader("Content-Type", "application/json"); configRequest.AddHeader("configName", configDesc.Name); configRequest.AddHeader("configOwnerId", this.MetaConfiguration.Parameter.First(p => p.Name == "clientId").Value); configRequest.RequestFormat = DataFormat.Json; var configResponse = await restClient.ExecuteAsync(configRequest); if (configResponse.StatusCode == System.Net.HttpStatusCode.OK) { var config = JsonConvert.DeserializeObject( configResponse.Content).First(); XmlDocument xmlDocument = new XmlDocument(); xmlDocument.LoadXml(config.Value); var processorNode = xmlDocument.GetElementsByTagName("processor")[0]; deviceProcessorConfiguration.Processor.Add(new Edge.Core.Configuration.Processor() { Name = processorNode.Attributes["name"].Value, //SerialNumber = processorNode.Attributes["serialNumber"]?.Value, DeviceProcessorType = processorNode.Attributes["deviceProcessorType"].Value, Complex = bool.Parse(processorNode.Attributes["complex"]?.Value ?? "false"), ConstructorArg = processorNode.Attributes["constructorArg"]?.Value, Parameter = processorNode.SelectNodes("parameter").Cast() .Select(n => new ProcessorParameter() { Complex = bool.Parse(n.Attributes["complex"]?.Value ?? "false"), Name = n.Attributes["name"].Value, Value = n.Attributes["value"]?.Value, ConstructorArg = n.Attributes["constructorArg"]?.Value, Description = n.Attributes["description"]?.Value }).ToList() }); } else { logger.LogInformation("Remote config service(api/Config/) returned with Status code: " + configResponse.StatusCode + " for Config with Name: " + configDesc.Name ?? ""); } } return deviceProcessorConfiguration; } /// /// It's the unnecessary config, HttpNotFound is acceptable, other status code will raise exception. /// /// /// /// private async Task ReadRemoteNozzleProductConfig( string configServiceBaseUrl, string configName) { var nozzleProductConfiguration = new NozzleExtraInfoConfiguration() { Mapping = new List() }; RestClient restClient = new RestClient(configServiceBaseUrl); var configRequest = new RestRequest("api/Config/", Method.Get); configRequest.AddHeader("Content-Type", "application/json"); configRequest.AddHeader("configName", configName); configRequest.AddHeader("configOwnerId", this.MetaConfiguration.Parameter.First(p => p.Name == "clientId").Value); configRequest.RequestFormat = DataFormat.Json; var configResponse = await restClient.ExecuteAsync(configRequest); if (configResponse.StatusCode == System.Net.HttpStatusCode.NotFound) { logger.LogInformation("Remote config service(api/Config/) retunred with Status Code: NotFound for config with name: " + configName); return null; } else if (configResponse.StatusCode != System.Net.HttpStatusCode.OK) { logger.LogInformation("Remote config service(api/Config/) returned with Status code: " + configResponse.StatusCode + " for config with name: " + configName); throw new ArgumentException("Remote config service(api/Config/) returned with Status code: " + configResponse.StatusCode); } var config = JsonConvert.DeserializeObject( configResponse.Content).First(); if (!config.Enabled) return nozzleProductConfiguration; XmlDocument xmlDocument = new XmlDocument(); xmlDocument.LoadXml(config.Value); var mappingNodes = xmlDocument.GetElementsByTagName("mapping"); foreach (var node in mappingNodes.Cast()) { nozzleProductConfiguration.Mapping.Add(new NozzleExtraInfo() { Description = node.Attributes["description"]?.Value, PumpId = int.Parse(node.Attributes["pumpId"]?.Value ?? "-1"), NozzleLogicalId = int.Parse(node.Attributes["nozzleLogicalId"]?.Value ?? "-1"), ProductBarcode = int.Parse(node.Attributes["productBarcode"]?.Value ?? "-1"), SiteLevelNozzleId = int.Parse(node.Attributes["siteLevelNozzleId"]?.Value ?? "-1"), }); } return nozzleProductConfiguration; } public static Configurator Default => instance; ///// ///// attach an Action for get notification once config file changed. ///// ///// will be called once config file changed //public void RegisterChangeCallback(Action callback) //{ // this.registeredChangeCallbacks.Add(callback); //} //public FdcServerConfiguration FdcServerConfiguration { get; set; } public DeviceProcessorConfiguration DeviceProcessorConfiguration { get; set; } public NozzleExtraInfoConfiguration NozzleExtraInfoConfiguration { get; set; } public MetaConfiguration MetaConfiguration { get; set; } } }