Configurator.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. using Edge.Core.Database;
  2. using Microsoft.Extensions.Configuration;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Text;
  7. using System.Linq;
  8. using System.Reflection;
  9. using RestSharp;
  10. using Newtonsoft.Json;
  11. using System.Threading.Tasks;
  12. using System.Xml;
  13. using System.Threading;
  14. using Microsoft.Extensions.Logging;
  15. using Microsoft.Extensions.Logging.Abstractions;
  16. using System.Xml.Serialization;
  17. namespace Edge.Core.Configuration
  18. {
  19. public class Configurator
  20. {
  21. public event Action<string> OnReadConfigInfo;
  22. private static Configurator instance = new Configurator();
  23. private Timer timelyCheckRemoteConfig;
  24. //private List<Action<object>> registeredChangeCallbacks = new List<Action<object>>();
  25. /// <summary>
  26. /// fired once the configuration file(settings.xml) changed.
  27. /// </summary>
  28. public event EventHandler OnConfigFileChanged;
  29. private IConfigurationRoot configurationRoot = null;
  30. public ILogger logger = NullLogger.Instance;
  31. private IConfigurationBuilder builder;
  32. public async Task<bool> LoadAsync()
  33. {
  34. try
  35. {
  36. //var ass = Assembly.GetExecutingAssembly();
  37. this.builder = new ConfigurationBuilder()
  38. .SetBasePath(Directory.GetCurrentDirectory())
  39. .AddJsonFile("noExistsNow.json", optional: true, reloadOnChange: true)
  40. .AddXmlFile("settings.xml", optional: false, reloadOnChange: true);
  41. this.configurationRoot = this.builder.Build();
  42. }
  43. catch (FileNotFoundException fnf)
  44. {
  45. Console.WriteLine(fnf);
  46. logger.LogError("Could not find local settings.xml");
  47. this.OnReadConfigInfo?.Invoke("Could not find local settings.xml");
  48. return false;
  49. }
  50. this.MetaConfiguration = this.RebindMetaFromFile();
  51. string enableAndRetrieveConfigFromConfigServiceUrl =
  52. this.MetaConfiguration.Parameter?.FirstOrDefault(p => p.Name
  53. == "enableAndRetrieveConfigFromConfigServiceUrl")?.Value;
  54. if (string.IsNullOrEmpty(enableAndRetrieveConfigFromConfigServiceUrl))
  55. {
  56. /* read config from local file */
  57. //logger.LogInformation("Use Local Config.");
  58. //this.OnReadConfigInfo?.Invoke("Use Local Config...");
  59. return this.LoadFromLocalFile();
  60. }
  61. else
  62. {
  63. /* read config from remote */
  64. logger.LogInformation("Use Remote Config against ConfigService url: " + enableAndRetrieveConfigFromConfigServiceUrl ?? "");
  65. this.OnReadConfigInfo?.Invoke("Use Remote Config: " + enableAndRetrieveConfigFromConfigServiceUrl ?? "");
  66. var loadResult = await this.LoadFromRemote(enableAndRetrieveConfigFromConfigServiceUrl);
  67. if (!loadResult) return false;
  68. // enable auto polling.
  69. if (this.timelyCheckRemoteConfig != null) this.timelyCheckRemoteConfig.Dispose();
  70. this.timelyCheckRemoteConfig = new Timer(async (o) =>
  71. {
  72. await this.LoadFromRemote(enableAndRetrieveConfigFromConfigServiceUrl);
  73. },
  74. null, 20000, 20000);
  75. return true;
  76. }
  77. }
  78. private bool LoadFromLocalFile()
  79. {
  80. this.configurationRoot.GetReloadToken().RegisterChangeCallback((o) =>
  81. {
  82. this.MetaConfiguration = this.RebindMetaFromFile();
  83. this.NozzleExtraInfoConfiguration = this.RebindNozzleProductFromFile();
  84. this.DeviceProcessorConfiguration = this.RebindDeviceProcessorFromFile();
  85. this.OnConfigFileChanged?.Invoke(this, null);
  86. this.configurationRoot = builder.Build();
  87. this.LoadFromLocalFile();
  88. //this.registeredChangeCallbacks.ForEach(c =>
  89. // // need improve, here just send a notification of the change
  90. // // but no specific processor.
  91. // c.Invoke(null));
  92. }, null);
  93. this.MetaConfiguration = this.RebindMetaFromFile();
  94. this.NozzleExtraInfoConfiguration = this.RebindNozzleProductFromFile();
  95. this.DeviceProcessorConfiguration = this.RebindDeviceProcessorFromFile();
  96. return true;
  97. }
  98. /// <summary>
  99. /// Read NozzleProduct and Processors configs from remote.
  100. /// </summary>
  101. /// <param name="enableAndRetrieveConfigFromConfigServiceUrl"></param>
  102. /// <returns></returns>
  103. private async Task<bool> LoadFromRemote(string enableAndRetrieveConfigFromConfigServiceUrl)
  104. {
  105. try
  106. {
  107. var newReadNozzleProductConfig = await this.ReadRemoteNozzleProductConfig(
  108. enableAndRetrieveConfigFromConfigServiceUrl,
  109. "LiteFccCoreSettings_NozzleProduct");
  110. if (newReadNozzleProductConfig != null)
  111. if (!newReadNozzleProductConfig.Equals(this.NozzleExtraInfoConfiguration))
  112. {
  113. logger.LogInformation("Remote Nozzle product config changed to:" + Environment.NewLine
  114. + this.ConvertObjectToString(newReadNozzleProductConfig) ?? ""
  115. + " will fire ChangeCallbacks...");
  116. this.OnReadConfigInfo?.Invoke("Remote Nozzle Product Config changed(read mapping count: " + newReadNozzleProductConfig.Mapping.Count + ")...");
  117. this.NozzleExtraInfoConfiguration = newReadNozzleProductConfig;
  118. this.OnConfigFileChanged?.Invoke(this, null);
  119. //this.registeredChangeCallbacks.ForEach(c => c.Invoke(this.NozzleProductConfiguration));
  120. }
  121. var newReadProcessorConfig = await this.ReadRemoteProcessorsConfig(
  122. enableAndRetrieveConfigFromConfigServiceUrl,
  123. "LiteFccCoreSettings_Processor");
  124. if (newReadProcessorConfig != null)
  125. if (!newReadProcessorConfig.Equals(this.DeviceProcessorConfiguration))
  126. {
  127. logger.LogInformation("Remote processors config changed to:" + Environment.NewLine
  128. + this.ConvertObjectToString(newReadProcessorConfig) ?? ""
  129. + " will fire ChangeCallbacks...");
  130. this.OnReadConfigInfo?.Invoke("Remote Processor Config changed(read processor count: " + newReadProcessorConfig.Processor.Count + ")...");
  131. this.DeviceProcessorConfiguration = newReadProcessorConfig;
  132. this.OnConfigFileChanged?.Invoke(this, null);
  133. //this.registeredChangeCallbacks.ForEach(c => c.Invoke(this.DeviceProcessorConfiguration));
  134. }
  135. return true;
  136. }
  137. catch (Exception exx)
  138. {
  139. logger.LogError("Configurator LoadFromRemote exceptioned: " + exx);
  140. this.OnReadConfigInfo?.Invoke("Read Remote Config error...");
  141. return false;
  142. }
  143. }
  144. private string ConvertObjectToString<T>(T config) where T : class
  145. {
  146. var loggingSerializer = new XmlSerializer(typeof(T));
  147. MemoryStream ms = new MemoryStream();
  148. loggingSerializer.Serialize(ms, config);
  149. ms.Position = 0;
  150. return new StreamReader(ms).ReadToEnd();
  151. }
  152. /// <summary>
  153. /// </summary>
  154. private DeviceProcessorConfiguration RebindDeviceProcessorFromFile()
  155. {
  156. var deviceProcessorConfiguration = new DeviceProcessorConfiguration();
  157. this.configurationRoot.GetSection("deviceProcessor").Bind(deviceProcessorConfiguration);
  158. return deviceProcessorConfiguration;
  159. }
  160. /// <summary>
  161. /// </summary>
  162. private NozzleExtraInfoConfiguration RebindNozzleProductFromFile()
  163. {
  164. var nozzleProductConfigurationFull = new NozzleExtraInfoConfiguration();
  165. this.configurationRoot.GetSection("nozzleProduct").Bind(nozzleProductConfigurationFull);
  166. return nozzleProductConfigurationFull;
  167. }
  168. /// <summary>
  169. /// </summary>
  170. private MetaConfiguration RebindMetaFromFile()
  171. {
  172. var metaConfigurationFull = new MetaConfiguration();
  173. configurationRoot.GetSection("meta").Bind(metaConfigurationFull);
  174. return metaConfigurationFull;
  175. }
  176. /// <summary>
  177. /// It's the necessary config, must be loaded from remote, otherwise exception.
  178. /// </summary>
  179. /// <param name="configServiceBaseUrl"></param>
  180. /// <param name="settingsGroupName"></param>
  181. /// <returns></returns>
  182. private async Task<DeviceProcessorConfiguration> ReadRemoteProcessorsConfig(
  183. string configServiceBaseUrl,
  184. string settingsGroupName)
  185. {
  186. RestClient restClient = new RestClient(configServiceBaseUrl);
  187. RestRequest configDescRequest = new RestRequest("api/Config/description", Method.GET);
  188. configDescRequest.AddHeader("Content-Type", "application/json");
  189. configDescRequest.AddQueryParameter("configGroupName", settingsGroupName);
  190. configDescRequest.AddQueryParameter("configOwnerIds", this.MetaConfiguration.Parameter.First(p => p.Name == "serialNumber").Value);
  191. configDescRequest.RequestFormat = DataFormat.Json;
  192. var configDescResponse = await restClient.ExecuteTaskAsync(configDescRequest);
  193. if (configDescResponse.StatusCode != System.Net.HttpStatusCode.OK)
  194. {
  195. logger.LogInformation("Remote config service(api/Config/description) returned with Status code: " + configDescResponse.StatusCode);
  196. throw new ArgumentException("Remote config service(api/Config/description) returned with Status code: " + configDescResponse.StatusCode);
  197. }
  198. var configDescriptions = JsonConvert.DeserializeObject<ConfigDescription[]>(configDescResponse.Content);
  199. var deviceProcessorConfiguration = new DeviceProcessorConfiguration() { Processor = new List<Processor>() };
  200. foreach (var configDesc in configDescriptions.Where(d => d.Enabled))
  201. {
  202. var configRequest = new RestRequest("api/Config/", Method.GET);
  203. configRequest.AddHeader("Content-Type", "application/json");
  204. configRequest.AddHeader("configName", configDesc.Name);
  205. configRequest.AddHeader("configOwnerId", this.MetaConfiguration.Parameter.First(p => p.Name == "clientId").Value);
  206. configRequest.RequestFormat = DataFormat.Json;
  207. var configResponse = await restClient.ExecuteTaskAsync(configRequest);
  208. if (configResponse.StatusCode == System.Net.HttpStatusCode.OK)
  209. {
  210. var config =
  211. JsonConvert.DeserializeObject<Edge.Core.Configuration.Configuration[]>(
  212. configResponse.Content).First();
  213. XmlDocument xmlDocument = new XmlDocument();
  214. xmlDocument.LoadXml(config.Value);
  215. var processorNode = xmlDocument.GetElementsByTagName("processor")[0];
  216. deviceProcessorConfiguration.Processor.Add(new Edge.Core.Configuration.Processor()
  217. {
  218. Name = processorNode.Attributes["name"].Value,
  219. //SerialNumber = processorNode.Attributes["serialNumber"]?.Value,
  220. DeviceProcessorType = processorNode.Attributes["deviceProcessorType"].Value,
  221. Complex = bool.Parse(processorNode.Attributes["complex"]?.Value ?? "false"),
  222. ConstructorArg = processorNode.Attributes["constructorArg"]?.Value,
  223. Parameter = processorNode.SelectNodes("parameter").Cast<XmlNode>()
  224. .Select(n => new ProcessorParameter()
  225. {
  226. Complex = bool.Parse(n.Attributes["complex"]?.Value ?? "false"),
  227. Name = n.Attributes["name"].Value,
  228. Value = n.Attributes["value"]?.Value,
  229. ConstructorArg = n.Attributes["constructorArg"]?.Value,
  230. Description = n.Attributes["description"]?.Value
  231. }).ToList()
  232. });
  233. }
  234. else
  235. {
  236. logger.LogInformation("Remote config service(api/Config/) returned with Status code: " + configResponse.StatusCode
  237. + " for Config with Name: " + configDesc.Name ?? "");
  238. }
  239. }
  240. return deviceProcessorConfiguration;
  241. }
  242. /// <summary>
  243. /// It's the unnecessary config, HttpNotFound is acceptable, other status code will raise exception.
  244. /// </summary>
  245. /// <param name="configServiceBaseUrl"></param>
  246. /// <param name="configName"></param>
  247. /// <returns></returns>
  248. private async Task<NozzleExtraInfoConfiguration> ReadRemoteNozzleProductConfig(
  249. string configServiceBaseUrl,
  250. string configName)
  251. {
  252. var nozzleProductConfiguration = new NozzleExtraInfoConfiguration() { Mapping = new List<NozzleExtraInfo>() };
  253. RestClient restClient = new RestClient(configServiceBaseUrl);
  254. var configRequest = new RestRequest("api/Config/", Method.GET);
  255. configRequest.AddHeader("Content-Type", "application/json");
  256. configRequest.AddHeader("configName", configName);
  257. configRequest.AddHeader("configOwnerId", this.MetaConfiguration.Parameter.First(p => p.Name == "clientId").Value);
  258. configRequest.RequestFormat = DataFormat.Json;
  259. var configResponse = await restClient.ExecuteTaskAsync(configRequest);
  260. if (configResponse.StatusCode == System.Net.HttpStatusCode.NotFound)
  261. {
  262. logger.LogInformation("Remote config service(api/Config/) retunred with Status Code: NotFound for config with name: " + configName);
  263. return null;
  264. }
  265. else if (configResponse.StatusCode != System.Net.HttpStatusCode.OK)
  266. {
  267. logger.LogInformation("Remote config service(api/Config/) returned with Status code: " + configResponse.StatusCode + " for config with name: " + configName);
  268. throw new ArgumentException("Remote config service(api/Config/) returned with Status code: " + configResponse.StatusCode);
  269. }
  270. var config =
  271. JsonConvert.DeserializeObject<Edge.Core.Configuration.Configuration[]>(
  272. configResponse.Content).First();
  273. if (!config.Enabled) return nozzleProductConfiguration;
  274. XmlDocument xmlDocument = new XmlDocument();
  275. xmlDocument.LoadXml(config.Value);
  276. var mappingNodes = xmlDocument.GetElementsByTagName("mapping");
  277. foreach (var node in mappingNodes.Cast<XmlNode>())
  278. {
  279. nozzleProductConfiguration.Mapping.Add(new NozzleExtraInfo()
  280. {
  281. Description = node.Attributes["description"]?.Value,
  282. PumpId = int.Parse(node.Attributes["pumpId"]?.Value ?? "-1"),
  283. NozzleLogicalId = int.Parse(node.Attributes["nozzleLogicalId"]?.Value ?? "-1"),
  284. ProductBarcode = int.Parse(node.Attributes["productBarcode"]?.Value ?? "-1"),
  285. SiteLevelNozzleId = int.Parse(node.Attributes["siteLevelNozzleId"]?.Value ?? "-1"),
  286. });
  287. }
  288. return nozzleProductConfiguration;
  289. }
  290. public static Configurator Default => instance;
  291. ///// <summary>
  292. ///// attach an Action for get notification once config file changed.
  293. ///// </summary>
  294. ///// <param name="callback">will be called once config file changed</param>
  295. //public void RegisterChangeCallback(Action<object> callback)
  296. //{
  297. // this.registeredChangeCallbacks.Add(callback);
  298. //}
  299. //public FdcServerConfiguration FdcServerConfiguration { get; set; }
  300. public DeviceProcessorConfiguration DeviceProcessorConfiguration { get; set; }
  301. public NozzleExtraInfoConfiguration NozzleExtraInfoConfiguration { get; set; }
  302. public MetaConfiguration MetaConfiguration { get; set; }
  303. }
  304. }