UniversalApiHub.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. using AutoMapper;
  2. using Edge.Core.Configuration;
  3. using Edge.Core.Database;
  4. using Edge.Core.Database.Models;
  5. using Edge.Core.Processor;
  6. using Edge.Core.Processor.Dispatcher.Attributes;
  7. using Edge.Core.UniversalApi.Auditing;
  8. using Microsoft.Extensions.DependencyInjection;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Linq;
  12. using System.Linq.Expressions;
  13. using System.Reflection;
  14. using System.Runtime.CompilerServices;
  15. using System.Text.Json;
  16. using System.Threading;
  17. using System.Threading.Tasks;
  18. namespace Edge.Core.UniversalApi
  19. {
  20. public class UniversalApiHub
  21. {
  22. private IEnumerable<IProcessor> processors;
  23. private IEnumerable<IAuditingStore> auditingStores;
  24. private IServiceProvider services;
  25. private bool config_DisableApiAuditing = false;
  26. private bool config_EnableApiInputAndOutputAuditing = false;
  27. public event EventHandler<UniversalGenericAlarmEventFiredEventArg> OnUniversalGenericAlarmEventFired;
  28. public async Task InitAsync(IEnumerable<IProcessor> processors)
  29. {
  30. this.processors = processors;
  31. foreach (var p in this.CommunicationProviders)
  32. {
  33. await p.SetupAsync(this.processors);
  34. }
  35. }
  36. public IEnumerable<ICommunicationProvider> CommunicationProviders { get; }
  37. public UniversalApiHub(IEnumerable<ICommunicationProvider> communicationProviders, IServiceProvider services)
  38. {
  39. this.CommunicationProviders = communicationProviders;
  40. this.services = services;
  41. var configurator = services.GetService<Configurator>();
  42. this.config_DisableApiAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
  43. ?.FirstOrDefault(p => p.Name.Equals("disableUniversalApiAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
  44. this.config_EnableApiInputAndOutputAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
  45. ?.FirstOrDefault(p => p.Name.Equals("enableUniversalApiInputAndOutputAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
  46. this.auditingStores = services.GetRequiredService<IEnumerable<IAuditingStore>>();
  47. configurator.OnConfigFileChanged += (_, __) =>
  48. {
  49. this.config_DisableApiAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
  50. ?.FirstOrDefault(p => p.Name.Equals("disableUniversalApiAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
  51. this.config_EnableApiInputAndOutputAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
  52. ?.FirstOrDefault(p => p.Name.Equals("enableUniversalApiInputAndOutputAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
  53. };
  54. }
  55. /// <summary>
  56. /// Fire an event to all underlying CommunicationProviders.
  57. /// Make sure class Attribute of `Event UniversalApi` declared in App or DeviceHandler.
  58. /// </summary>
  59. /// <param name="source"></param>
  60. /// <param name="eventName"></param>
  61. /// <param name="eventData"></param>
  62. /// <returns></returns>
  63. public async Task FireEvent(IProcessor source, string eventName, object eventData)
  64. {
  65. #region
  66. AuditLogInfo auditLogInfo = null;
  67. if (!this.config_DisableApiAuditing)
  68. {
  69. auditLogInfo = new AuditLogInfo()
  70. {
  71. ClientIdentity = "",
  72. DeviceHandlerOrAppName = source.SelectHandlerOrAppThenCast<object>().GetType().FullName,
  73. ExecutionTime = DateTime.Now,
  74. };
  75. auditLogInfo.Actions.Add(new AuditLogActionInfo()
  76. {
  77. ApiName = "event : " + eventName,
  78. InputParameters =
  79. this.config_EnableApiInputAndOutputAuditing ?
  80. JsonSerializer.Serialize(eventData) : null,
  81. });
  82. auditLogInfo.Prepare();
  83. }
  84. #endregion
  85. try
  86. {
  87. if (this.CommunicationProviders == null) return;
  88. foreach (var provider in this.CommunicationProviders)
  89. {
  90. try
  91. {
  92. var executeResult = await provider.RouteEventAsync(source,
  93. new EventDescriptor() { Name = eventName, Data = eventData });
  94. }
  95. catch (Exception exxx)
  96. {
  97. #region
  98. //if (!this.config_DisableApiAuditing)
  99. //{ auditLogInfo.CommitWithExceptions(new Exception[] { exxx }); }
  100. #endregion
  101. }
  102. }
  103. }
  104. finally
  105. {
  106. if (!this.config_DisableApiAuditing)
  107. {
  108. auditLogInfo.CommitWithExeResult(true);
  109. Task.WaitAll(this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray());
  110. }
  111. }
  112. }
  113. /// <summary>
  114. /// Fire generic event without persist it(like fire and forget).
  115. /// the UI component would receive it at the moment and only for onetime, to showing a message to user.
  116. /// </summary>
  117. /// <param name="source"></param>
  118. /// <param name="genericAlarm"></param>
  119. /// <returns></returns>
  120. public Task FireGenericAlarm(IProcessor source, GenericAlarm genericAlarm)
  121. {
  122. return this.FireGenericAlarm(source, genericAlarm, false, null);
  123. }
  124. /// <summary>
  125. /// Fire generic event and persist it.
  126. /// the UI component would receive it at the moment and can retrieve it anytime later, to showing a message to user.
  127. /// </summary>
  128. /// <param name="source"></param>
  129. /// <param name="genericAlarm"></param>
  130. /// <param name="hiddenDataSelector">any custom data to persist together with this alarm, used for searching.</param>
  131. /// <returns>the persist record id, use it for retrieve back the record.</returns>
  132. public Task<int?> FirePersistGenericAlarm(IProcessor source, GenericAlarm genericAlarm, Func<GenericAlarm, string> hiddenDataSelector)
  133. {
  134. return this.FireGenericAlarm(source, genericAlarm, true, hiddenDataSelector(genericAlarm));
  135. }
  136. /// <summary>
  137. ///
  138. /// </summary>
  139. /// <param name="source"></param>
  140. /// <param name="genericAlarm"></param>
  141. /// <param name="persist">if true, the alarm will be persist</param>
  142. /// <param name="hiddenData"></param>
  143. /// <param name="muteFiring">not firing the event, used for scenario that refresh the persist alarm without re-firing it.</param>
  144. /// <returns></returns>
  145. private async Task<int?> FireGenericAlarm(IProcessor source, GenericAlarm genericAlarm, bool persist, string hiddenData, bool muteFiring = false)
  146. {
  147. int? persistAlarmDbRecordKey = null;
  148. if (!persist)
  149. {
  150. }
  151. else
  152. {
  153. var mapper = this.services.GetRequiredService<IMapper>();
  154. using (var scope = this.services.CreateScope())
  155. {
  156. }
  157. }
  158. if (!muteFiring)
  159. this.OnUniversalGenericAlarmEventFired?.Invoke(this, new UniversalGenericAlarmEventFiredEventArg(source, genericAlarm, persistAlarmDbRecordKey));
  160. //await this.FireEvent(source, GenericAlarm.UniversalApiEventName, genericAlarm);
  161. return persistAlarmDbRecordKey;
  162. }
  163. /// <summary>
  164. /// Mark several Opening state alarms to Closed state, and fill with close reason for each.
  165. /// </summary>
  166. /// <param name="alarmIdAndCloseReasons">list of opening state alarm id and close reason.</param>
  167. /// <returns></returns>
  168. public async Task ClosePersistGenericAlarms(IEnumerable<Tuple<int, string>> alarmIdAndCloseReasons)
  169. {
  170. using (var scope = this.services.CreateScope())
  171. {
  172. //await dbContext.SaveChangesAsync();
  173. }
  174. }
  175. public async Task AckPersistGenericAlarms(IEnumerable<Tuple<int, string>> alarmIdAndAckReasons, bool isForUnAck = false)
  176. {
  177. using (var scope = this.services.CreateScope())
  178. {
  179. // await dbContext.SaveChangesAsync();
  180. }
  181. }
  182. /// <summary>
  183. /// Mark all Opening state alarms to Closed state, and fill with the same close reason, for a specific processor.
  184. /// </summary>
  185. /// <param name="source">all alarms for this processor will be closed.</param>
  186. /// <param name="closeReason">all target alarms will set with this close reason</param>
  187. /// <returns></returns>
  188. public Task<int> ClosePersistGenericAlarms(IProcessor source, string closeReason)
  189. {
  190. return this.ClosePersistGenericAlarms(source, null, closeReason);
  191. }
  192. /// <summary>
  193. /// Mark all Opening state alarms to Closed state, and fill with the same close reason.
  194. /// </summary>
  195. /// <param name="source"></param>
  196. /// <param name="hiddenDataHint">the searching criteria, the hidden data fields contains this value will be matched.</param>
  197. /// <param name="closeReason">all target alarms will set with this close reason</param>
  198. /// <returns></returns>
  199. public async Task<int> ClosePersistGenericAlarms(IProcessor source, string hiddenDataHint, string closeReason)
  200. {
  201. var originator = source.ProcessorDescriptor()?.DeviceHandlerOrApp?.GetType()
  202. ?.GetCustomAttributes<MetaPartsDescriptor>()?.FirstOrDefault()?.DisplayName;
  203. if (string.IsNullOrEmpty(originator))
  204. originator = source.ProcessorDescriptor()?.DeviceHandlerOrApp?.GetType().FullName;
  205. using (var scope = this.services.CreateScope())
  206. {
  207. //var effectRowCount = await dbContext.SaveChangesAsync();
  208. return 1;
  209. }
  210. }
  211. /// <summary>
  212. /// Query the persist generic alarms by search conditions.
  213. /// </summary>
  214. /// <param name="source">only alarms from this processor are returned, leave null to ignore this constrait.</param>
  215. /// <param name="originators">null to ignore this constrait.</param>
  216. /// <param name="alarmCategories">null to ignore this constrait.</param>
  217. /// <param name="alarmSubCategories">null to ignore this constrait.</param>
  218. /// <param name="alarmHiddenData">null to ignore this constrait.</param>
  219. /// <param name="alarmOpeningDateTimeFrom">null to ignore this constrait.</param>
  220. /// <param name="alarmOpeningDateTimeTo">null to ignore this constrait.</param>
  221. /// <param name="severity">null to ignore this constrait.</param>
  222. /// <param name="includeAckedStateAlarm"></param>
  223. /// <param name="includeClosedStateAlarm"></param>
  224. /// <param name="pageIndex"></param>
  225. /// <param name="pageRowCount"></param>
  226. /// <returns></returns>
  227. public async Task<IEnumerable<GenericAlarmDbModel>> GetPersistGenericAlarms(
  228. IProcessor source,
  229. string[] originators,
  230. string[] alarmCategories,
  231. string[] alarmSubCategories,
  232. string alarmHiddenData,
  233. DateTime? alarmOpeningDateTimeFrom, DateTime? alarmOpeningDateTimeTo,
  234. GenericAlarmSeverity? severity,
  235. bool includeAckedStateAlarm,
  236. bool includeClosedStateAlarm,
  237. int pageIndex, int pageRowCount)
  238. {
  239. using (var scope = this.services.CreateScope())
  240. {
  241. return null;
  242. //return r.Select(d => new
  243. //{
  244. // d.Id,
  245. // d.Originator,
  246. // d.Severity,
  247. // d.Category,
  248. // d.SubCategory,
  249. // d.Title,
  250. // d.Detail,
  251. // d.ClosedTimestamp,
  252. // d.ClosedReason,
  253. // d.Action,
  254. //});
  255. }
  256. }
  257. /// <summary>
  258. /// Close the alarms by match its hidden data, and create a new persist generic alarm.
  259. /// </summary>
  260. /// <param name="source"></param>
  261. /// <param name="closingAlarmsHiddenDataPredicate">predictor for hidden data match for test with all exist alarms, the matched ones will be all closed.</param>
  262. /// <param name="closeReason"></param>
  263. /// <param name="newAlarm"></param>
  264. /// <param name="newAlarmHiddenDataSelector">generator for hidden data that will be used for create a new alarm.</param>
  265. /// <param name="muteNewAlarmFiring">if true, then new alarm event will not be fired, used for scenario that create an alarm but not firing event to gain user attention.</param>
  266. /// <returns></returns>
  267. public async Task<int?> CloseAndFirePersistGenericAlarm(IProcessor source, GenericAlarm newAlarm,
  268. Func<GenericAlarm, string> closingAlarmsHiddenDataPredicate, string closeReason,
  269. Func<GenericAlarm, string> newAlarmHiddenDataSelector, bool muteNewAlarmFiring = false)
  270. {
  271. if (source == null) throw new ArgumentNullException(nameof(source));
  272. bool lockTaken = false;
  273. try
  274. {
  275. this.spinLock_GuardTwoStageOperation.Enter(ref lockTaken);
  276. var alarmsForClosing = await GetPersistGenericAlarms(source, null, null, null, closingAlarmsHiddenDataPredicate(newAlarm), null, null, null, true, false, 0, 100);
  277. if (alarmsForClosing.Any())
  278. await this.ClosePersistGenericAlarms(alarmsForClosing.Select(c => new Tuple<int, string>(c.Id, closeReason)));
  279. var r = await this.FireGenericAlarm(source, newAlarm, true, newAlarmHiddenDataSelector(newAlarm), muteNewAlarmFiring);
  280. return r;
  281. }
  282. finally
  283. {
  284. if (lockTaken) this.spinLock_GuardTwoStageOperation.Exit(false);
  285. }
  286. }
  287. private SpinLock spinLock_GuardTwoStageOperation = new SpinLock();
  288. /// <summary>
  289. /// Clear(remove from db) the alarms by match its hidden data, and create a new persist generic alarm.
  290. /// </summary>
  291. /// <param name="source"></param>
  292. /// <param name="newAlarm"></param>
  293. /// <param name="clearingAlarmsHiddenDataPredicate">predictor for hidden data match for test with all exist alarms, the matched ones will be all cleared.</param>
  294. /// <param name="newAlarmHiddenDataSelector"></param>
  295. /// <param name="muteNewAlarmFiring">if true, then new alarm event will not be fired, used for scenario that create an alarm but not firing event to gain user attention.</param>
  296. /// <returns></returns>
  297. public async Task<int?> ClearAndFirePersistGenericAlarm(IProcessor source, GenericAlarm newAlarm,
  298. Func<GenericAlarm, string> clearingAlarmsHiddenDataPredicate,
  299. Func<GenericAlarm, string> newAlarmHiddenDataSelector, bool muteNewAlarmFiring = false)
  300. {
  301. if (source == null) throw new ArgumentNullException(nameof(source));
  302. bool lockTaken = false;
  303. try
  304. {
  305. this.spinLock_GuardTwoStageOperation.Enter(ref lockTaken);
  306. using (var scope = this.services.CreateScope())
  307. {
  308. }
  309. var r = await this.FireGenericAlarm(source, newAlarm, true, newAlarmHiddenDataSelector(newAlarm), muteNewAlarmFiring);
  310. return r;
  311. }
  312. finally
  313. {
  314. if (lockTaken) this.spinLock_GuardTwoStageOperation.Exit(false);
  315. }
  316. }
  317. /// <summary>
  318. /// Try firing an alarm by only there's no matched opened alarm in db.
  319. /// </summary>
  320. /// <param name="source"></param>
  321. /// <param name="newAlarm"></param>
  322. /// <param name="existsAlarmsHiddenDataPredicate">predictor for hidden data match for test with all exist alarms, any matched ones will be considered as EXISTS, and then will not firing anymore.</param>
  323. /// <param name="newAlarmHiddenDataSelector"></param>
  324. /// <returns></returns>
  325. public async Task<int?> FirePersistGenericAlarmIfNotExists(IProcessor source, GenericAlarm newAlarm,
  326. Func<GenericAlarm, string> existsAlarmsHiddenDataPredicate,
  327. Func<GenericAlarm, string> newAlarmHiddenDataSelector)
  328. {
  329. if (source == null) throw new ArgumentNullException(nameof(source));
  330. bool lockTaken = false;
  331. try
  332. {
  333. this.spinLock_GuardTwoStageOperation.Enter(ref lockTaken);
  334. using (var scope = this.services.CreateScope())
  335. {
  336. return null;
  337. }
  338. }
  339. finally
  340. {
  341. if (lockTaken) this.spinLock_GuardTwoStageOperation.Exit(false);
  342. }
  343. }
  344. }
  345. public class UniversalGenericAlarmEventFiredEventArg : System.EventArgs
  346. {
  347. public IProcessor Originator { get; }
  348. public GenericAlarm GenericAlarm { get; }
  349. public int? PersistId { get; }
  350. public UniversalGenericAlarmEventFiredEventArg(IProcessor originator, GenericAlarm genericAlarm, int? persistId)
  351. {
  352. this.Originator = originator;
  353. this.GenericAlarm = genericAlarm;
  354. this.PersistId = persistId;
  355. }
  356. }
  357. }