UniversalApiHub.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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.EntityFrameworkCore;
  9. using Microsoft.Extensions.DependencyInjection;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Linq;
  13. using System.Linq.Expressions;
  14. using System.Reflection;
  15. using System.Runtime.CompilerServices;
  16. using System.Text.Json;
  17. using System.Threading;
  18. using System.Threading.Tasks;
  19. namespace Edge.Core.UniversalApi
  20. {
  21. public class UniversalApiHub
  22. {
  23. private IEnumerable<IProcessor> processors;
  24. private IEnumerable<IAuditingStore> auditingStores;
  25. private IServiceProvider services;
  26. private bool config_DisableApiAuditing = false;
  27. private bool config_EnableApiInputAndOutputAuditing = false;
  28. public event EventHandler<UniversalGenericAlarmEventFiredEventArg> OnUniversalGenericAlarmEventFired;
  29. public async Task InitAsync(IEnumerable<IProcessor> processors)
  30. {
  31. this.processors = processors;
  32. foreach (var p in this.CommunicationProviders)
  33. {
  34. await p.SetupAsync(this.processors);
  35. }
  36. }
  37. public IEnumerable<ICommunicationProvider> CommunicationProviders { get; }
  38. public UniversalApiHub(IEnumerable<ICommunicationProvider> communicationProviders, IServiceProvider services)
  39. {
  40. this.CommunicationProviders = communicationProviders;
  41. this.services = services;
  42. var configurator = services.GetService<Configurator>();
  43. this.config_DisableApiAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
  44. ?.FirstOrDefault(p => p.Name.Equals("disableUniversalApiAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
  45. this.config_EnableApiInputAndOutputAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
  46. ?.FirstOrDefault(p => p.Name.Equals("enableUniversalApiInputAndOutputAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
  47. this.auditingStores = services.GetRequiredService<IEnumerable<IAuditingStore>>();
  48. configurator.OnConfigFileChanged += (_, __) =>
  49. {
  50. this.config_DisableApiAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
  51. ?.FirstOrDefault(p => p.Name.Equals("disableUniversalApiAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
  52. this.config_EnableApiInputAndOutputAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
  53. ?.FirstOrDefault(p => p.Name.Equals("enableUniversalApiInputAndOutputAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
  54. };
  55. }
  56. /// <summary>
  57. /// Fire an event to all underlying CommunicationProviders.
  58. /// Make sure class Attribute of `Event UniversalApi` declared in App or DeviceHandler.
  59. /// </summary>
  60. /// <param name="source"></param>
  61. /// <param name="eventName"></param>
  62. /// <param name="eventData"></param>
  63. /// <returns></returns>
  64. public async Task FireEvent(IProcessor source, string eventName, object eventData)
  65. {
  66. #region
  67. AuditLogInfo auditLogInfo = null;
  68. if (!this.config_DisableApiAuditing)
  69. {
  70. auditLogInfo = new AuditLogInfo()
  71. {
  72. ClientIdentity = "",
  73. DeviceHandlerOrAppName = source.SelectHandlerOrAppThenCast<object>().GetType().FullName,
  74. ExecutionTime = DateTime.Now,
  75. };
  76. auditLogInfo.Actions.Add(new AuditLogActionInfo()
  77. {
  78. ApiName = "event : " + eventName,
  79. InputParameters =
  80. this.config_EnableApiInputAndOutputAuditing ?
  81. JsonSerializer.Serialize(eventData) : null,
  82. });
  83. auditLogInfo.Prepare();
  84. }
  85. #endregion
  86. try
  87. {
  88. if (this.CommunicationProviders == null) return;
  89. foreach (var provider in this.CommunicationProviders)
  90. {
  91. try
  92. {
  93. var executeResult = await provider.RouteEventAsync(source,
  94. new EventDescriptor() { Name = eventName, Data = eventData });
  95. }
  96. catch (Exception exxx)
  97. {
  98. #region
  99. //if (!this.config_DisableApiAuditing)
  100. //{ auditLogInfo.CommitWithExceptions(new Exception[] { exxx }); }
  101. #endregion
  102. }
  103. }
  104. }
  105. finally
  106. {
  107. if (!this.config_DisableApiAuditing)
  108. {
  109. auditLogInfo.CommitWithExeResult(true);
  110. Task.WaitAll(this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray());
  111. }
  112. }
  113. }
  114. /// <summary>
  115. /// Fire generic event without persist it(like fire and forget).
  116. /// the UI component would receive it at the moment and only for onetime, to showing a message to user.
  117. /// </summary>
  118. /// <param name="source"></param>
  119. /// <param name="genericAlarm"></param>
  120. /// <returns></returns>
  121. public Task FireGenericAlarm(IProcessor source, GenericAlarm genericAlarm)
  122. {
  123. return this.FireGenericAlarm(source, genericAlarm, false, null);
  124. }
  125. /// <summary>
  126. /// Fire generic event and persist it.
  127. /// the UI component would receive it at the moment and can retrieve it anytime later, to showing a message to user.
  128. /// </summary>
  129. /// <param name="source"></param>
  130. /// <param name="genericAlarm"></param>
  131. /// <param name="hiddenDataSelector">any custom data to persist together with this alarm, used for searching.</param>
  132. /// <returns>the persist record id, use it for retrieve back the record.</returns>
  133. public Task<int?> FirePersistGenericAlarm(IProcessor source, GenericAlarm genericAlarm, Func<GenericAlarm, string> hiddenDataSelector)
  134. {
  135. return this.FireGenericAlarm(source, genericAlarm, true, hiddenDataSelector(genericAlarm));
  136. }
  137. /// <summary>
  138. ///
  139. /// </summary>
  140. /// <param name="source"></param>
  141. /// <param name="genericAlarm"></param>
  142. /// <param name="persist">if true, the alarm will be persist</param>
  143. /// <param name="hiddenData"></param>
  144. /// <param name="muteFiring">not firing the event, used for scenario that refresh the persist alarm without re-firing it.</param>
  145. /// <returns></returns>
  146. private async Task<int?> FireGenericAlarm(IProcessor source, GenericAlarm genericAlarm, bool persist, string hiddenData, bool muteFiring = false)
  147. {
  148. int? persistAlarmDbRecordKey = null;
  149. if (!persist)
  150. {
  151. }
  152. else
  153. {
  154. var mapper = this.services.GetRequiredService<IMapper>();
  155. using (var scope = this.services.CreateScope())
  156. {
  157. var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
  158. var pDesc = source.ProcessorDescriptor();
  159. var originatorDisplayName = pDesc?.DeviceHandlerOrApp?.GetType()
  160. ?.GetCustomAttributes<MetaPartsDescriptor>()?.FirstOrDefault()?.DisplayName;
  161. if (string.IsNullOrEmpty(originatorDisplayName))
  162. originatorDisplayName = pDesc?.DeviceHandlerOrApp?.GetType().FullName;
  163. var dbAlarm = mapper.Map<GenericAlarmDbModel>(genericAlarm);
  164. dbAlarm.ProcessorEndpointFullTypeStr = pDesc?.DeviceHandlerOrApp?.GetType().FullName;
  165. dbAlarm.OriginatorDisplayName = originatorDisplayName;
  166. dbAlarm.OpeningTimestamp = DateTime.Now;
  167. dbAlarm.HiddenData = hiddenData;
  168. dbContext.Add(dbAlarm);
  169. if (DateTime.Now.Second % 5 == 0)
  170. {
  171. //start purging old datas
  172. DateTime due = DateTime.Now.Subtract(new TimeSpan(60, 0, 0, 0));
  173. var removing = await dbContext.GenericAlarms.Where(ga => ga.OpeningTimestamp <= due).ToArrayAsync();
  174. dbContext.RemoveRange(removing);
  175. }
  176. await dbContext.SaveChangesAsync();
  177. persistAlarmDbRecordKey = dbAlarm.Id;
  178. }
  179. }
  180. if (!muteFiring)
  181. this.OnUniversalGenericAlarmEventFired?.Invoke(this, new UniversalGenericAlarmEventFiredEventArg(source, genericAlarm, persistAlarmDbRecordKey));
  182. //await this.FireEvent(source, GenericAlarm.UniversalApiEventName, genericAlarm);
  183. return persistAlarmDbRecordKey;
  184. }
  185. /// <summary>
  186. /// Mark several Opening state alarms to Closed state, and fill with close reason for each.
  187. /// </summary>
  188. /// <param name="alarmIdAndCloseReasons">list of opening state alarm id and close reason.</param>
  189. /// <returns></returns>
  190. public async Task ClosePersistGenericAlarms(IEnumerable<Tuple<int, string>> alarmIdAndCloseReasons)
  191. {
  192. using (var scope = this.services.CreateScope())
  193. {
  194. var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
  195. foreach (var idAndCloseReason in alarmIdAndCloseReasons)
  196. {
  197. var target = dbContext.GenericAlarms.Find(idAndCloseReason.Item1);
  198. if (target == null) continue;
  199. target.ClosedTimestamp = DateTime.Now;
  200. target.ClosedReason = idAndCloseReason.Item2;
  201. dbContext.GenericAlarms.Update(target);
  202. }
  203. await dbContext.SaveChangesAsync();
  204. }
  205. }
  206. public async Task AckPersistGenericAlarms(IEnumerable<Tuple<int, string>> alarmIdAndAckReasons, bool isForUnAck = false)
  207. {
  208. using (var scope = this.services.CreateScope())
  209. {
  210. var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
  211. foreach (var idAndAckReason in alarmIdAndAckReasons)
  212. {
  213. var target = dbContext.GenericAlarms.Find(idAndAckReason.Item1);
  214. if (isForUnAck)
  215. {
  216. target.AckedTimestamp = null;
  217. target.AckedReason = null;
  218. }
  219. else
  220. {
  221. target.AckedTimestamp = DateTime.Now;
  222. target.AckedReason = idAndAckReason.Item2;
  223. }
  224. dbContext.GenericAlarms.Update(target);
  225. }
  226. await dbContext.SaveChangesAsync();
  227. }
  228. }
  229. /// <summary>
  230. /// Mark all Opening state alarms to Closed state, and fill with the same close reason, for a specific processor.
  231. /// </summary>
  232. /// <param name="source">all alarms for this processor will be closed.</param>
  233. /// <param name="closeReason">all target alarms will set with this close reason</param>
  234. /// <returns></returns>
  235. public Task<int> ClosePersistGenericAlarms(IProcessor source, string closeReason)
  236. {
  237. return this.ClosePersistGenericAlarms(source, null, closeReason);
  238. }
  239. /// <summary>
  240. /// Mark all Opening state alarms to Closed state, and fill with the same close reason.
  241. /// </summary>
  242. /// <param name="source"></param>
  243. /// <param name="hiddenDataHint">the searching criteria, the hidden data fields contains this value will be matched.</param>
  244. /// <param name="closeReason">all target alarms will set with this close reason</param>
  245. /// <returns></returns>
  246. public async Task<int> ClosePersistGenericAlarms(IProcessor source, string hiddenDataHint, string closeReason)
  247. {
  248. var originator = source.ProcessorDescriptor()?.DeviceHandlerOrApp?.GetType()
  249. ?.GetCustomAttributes<MetaPartsDescriptor>()?.FirstOrDefault()?.DisplayName;
  250. if (string.IsNullOrEmpty(originator))
  251. originator = source.ProcessorDescriptor()?.DeviceHandlerOrApp?.GetType().FullName;
  252. using (var scope = this.services.CreateScope())
  253. {
  254. var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
  255. IQueryable<GenericAlarmDbModel> ownedOpenAlarms;
  256. if (string.IsNullOrEmpty(hiddenDataHint))
  257. ownedOpenAlarms = dbContext.GenericAlarms.Where(ga => ga.OriginatorDisplayName == originator && ga.ClosedTimestamp == null);
  258. else
  259. ownedOpenAlarms = dbContext.GenericAlarms.Where(ga => ga.OriginatorDisplayName == originator && ga.HiddenData.Contains(hiddenDataHint) && ga.ClosedTimestamp == null);
  260. foreach (var al in ownedOpenAlarms)
  261. {
  262. al.ClosedTimestamp = DateTime.Now;
  263. al.ClosedReason = closeReason;
  264. dbContext.GenericAlarms.Update(al);
  265. }
  266. var effectRowCount = await dbContext.SaveChangesAsync();
  267. return effectRowCount;
  268. }
  269. }
  270. /// <summary>
  271. /// Query the persist generic alarms by search conditions.
  272. /// </summary>
  273. /// <param name="source">only alarms from this processor are returned, leave null to ignore this constrait.</param>
  274. /// <param name="originators">null to ignore this constrait.</param>
  275. /// <param name="alarmCategories">null to ignore this constrait.</param>
  276. /// <param name="alarmSubCategories">null to ignore this constrait.</param>
  277. /// <param name="alarmHiddenData">null to ignore this constrait.</param>
  278. /// <param name="alarmOpeningDateTimeFrom">null to ignore this constrait.</param>
  279. /// <param name="alarmOpeningDateTimeTo">null to ignore this constrait.</param>
  280. /// <param name="severity">null to ignore this constrait.</param>
  281. /// <param name="includeAckedStateAlarm"></param>
  282. /// <param name="includeClosedStateAlarm"></param>
  283. /// <param name="pageIndex"></param>
  284. /// <param name="pageRowCount"></param>
  285. /// <returns></returns>
  286. public async Task<IEnumerable<GenericAlarmDbModel>> GetPersistGenericAlarms(
  287. IProcessor source,
  288. string[] originators,
  289. string[] alarmCategories,
  290. string[] alarmSubCategories,
  291. string alarmHiddenData,
  292. DateTime? alarmOpeningDateTimeFrom, DateTime? alarmOpeningDateTimeTo,
  293. GenericAlarmSeverity? severity,
  294. bool includeAckedStateAlarm,
  295. bool includeClosedStateAlarm,
  296. int pageIndex, int pageRowCount)
  297. {
  298. using (var scope = this.services.CreateScope())
  299. {
  300. var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
  301. IQueryable<GenericAlarmDbModel> data = dbContext.GenericAlarms;
  302. if (alarmOpeningDateTimeFrom != null)
  303. data = data.Where(ga => ga.OpeningTimestamp >= alarmOpeningDateTimeFrom);
  304. if (alarmOpeningDateTimeTo != null)
  305. data = data.Where(ga => ga.OpeningTimestamp <= alarmOpeningDateTimeTo);
  306. if (source != null)
  307. {
  308. var typeFullName = source.ProcessorDescriptor()?.DeviceHandlerOrApp?.GetType().FullName;
  309. data = data.Where(ga => ga.ProcessorEndpointFullTypeStr == typeFullName);
  310. }
  311. if (originators != null && originators.Any())
  312. {
  313. var sqlRaw = $"SELECT * FROM GenericAlarms where {originators.Select(input => "OriginatorDisplayName like '%" + input + "%' ").Aggregate((acc, n) => acc + " or " + n)}";
  314. data = ((DbSet<GenericAlarmDbModel>)data).FromSqlRaw(sqlRaw);
  315. }
  316. if (alarmCategories != null)
  317. {
  318. var sqlRaw = $"SELECT * FROM GenericAlarms where {alarmCategories.Select(input => "Category like '%" + input + "%' ").Aggregate((acc, n) => acc + " or " + n)}";
  319. data = ((DbSet<GenericAlarmDbModel>)data).FromSqlRaw(sqlRaw);
  320. }
  321. if (alarmSubCategories != null)
  322. {
  323. var sqlRaw = $"SELECT * FROM GenericAlarms where {alarmSubCategories.Select(input => "SubCategory like '%" + input + "%' ").Aggregate((acc, n) => acc + " or " + n)}";
  324. data = ((DbSet<GenericAlarmDbModel>)data).FromSqlRaw(sqlRaw);
  325. }
  326. if (alarmHiddenData != null)
  327. data = data.Where(ga => ga.HiddenData == alarmHiddenData);
  328. if (severity != null)
  329. data = data.Where(ga => ga.Severity == severity);
  330. if (!includeClosedStateAlarm)
  331. data = data.Where(ga => ga.ClosedTimestamp == null);
  332. if (!includeAckedStateAlarm)
  333. data = data.Where(ga => ga.AckedTimestamp == null);
  334. var r = await data.OrderByDescending(d => d.OpeningTimestamp).Skip(pageIndex * pageRowCount).Take(pageRowCount).ToListAsync();
  335. return r;
  336. //return r.Select(d => new
  337. //{
  338. // d.Id,
  339. // d.Originator,
  340. // d.Severity,
  341. // d.Category,
  342. // d.SubCategory,
  343. // d.Title,
  344. // d.Detail,
  345. // d.ClosedTimestamp,
  346. // d.ClosedReason,
  347. // d.Action,
  348. //});
  349. }
  350. }
  351. /// <summary>
  352. /// Close the alarms by match its hidden data, and create a new persist generic alarm.
  353. /// </summary>
  354. /// <param name="source"></param>
  355. /// <param name="closingAlarmsHiddenDataPredicate">predictor for hidden data match for test with all exist alarms, the matched ones will be all closed.</param>
  356. /// <param name="closeReason"></param>
  357. /// <param name="newAlarm"></param>
  358. /// <param name="newAlarmHiddenDataSelector">generator for hidden data that will be used for create a new alarm.</param>
  359. /// <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>
  360. /// <returns></returns>
  361. public async Task<int?> CloseAndFirePersistGenericAlarm(IProcessor source, GenericAlarm newAlarm,
  362. Func<GenericAlarm, string> closingAlarmsHiddenDataPredicate, string closeReason,
  363. Func<GenericAlarm, string> newAlarmHiddenDataSelector, bool muteNewAlarmFiring = false)
  364. {
  365. if (source == null) throw new ArgumentNullException(nameof(source));
  366. bool lockTaken = false;
  367. try
  368. {
  369. this.spinLock_GuardTwoStageOperation.Enter(ref lockTaken);
  370. var alarmsForClosing = await GetPersistGenericAlarms(source, null, null, null, closingAlarmsHiddenDataPredicate(newAlarm), null, null, null, true, false, 0, 100);
  371. if (alarmsForClosing.Any())
  372. await this.ClosePersistGenericAlarms(alarmsForClosing.Select(c => new Tuple<int, string>(c.Id, closeReason)));
  373. var r = await this.FireGenericAlarm(source, newAlarm, true, newAlarmHiddenDataSelector(newAlarm), muteNewAlarmFiring);
  374. return r;
  375. }
  376. finally
  377. {
  378. if (lockTaken) this.spinLock_GuardTwoStageOperation.Exit(false);
  379. }
  380. }
  381. private SpinLock spinLock_GuardTwoStageOperation = new SpinLock();
  382. /// <summary>
  383. /// Clear(remove from db) the alarms by match its hidden data, and create a new persist generic alarm.
  384. /// </summary>
  385. /// <param name="source"></param>
  386. /// <param name="newAlarm"></param>
  387. /// <param name="clearingAlarmsHiddenDataPredicate">predictor for hidden data match for test with all exist alarms, the matched ones will be all cleared.</param>
  388. /// <param name="newAlarmHiddenDataSelector"></param>
  389. /// <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>
  390. /// <returns></returns>
  391. public async Task<int?> ClearAndFirePersistGenericAlarm(IProcessor source, GenericAlarm newAlarm,
  392. Func<GenericAlarm, string> clearingAlarmsHiddenDataPredicate,
  393. Func<GenericAlarm, string> newAlarmHiddenDataSelector, bool muteNewAlarmFiring = false)
  394. {
  395. if (source == null) throw new ArgumentNullException(nameof(source));
  396. bool lockTaken = false;
  397. try
  398. {
  399. this.spinLock_GuardTwoStageOperation.Enter(ref lockTaken);
  400. using (var scope = this.services.CreateScope())
  401. {
  402. var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
  403. var hd = clearingAlarmsHiddenDataPredicate(newAlarm);
  404. var typeFullName = source.ProcessorDescriptor()?.DeviceHandlerOrApp?.GetType().FullName;
  405. var deleting = dbContext.GenericAlarms.Where(ga => ga.ProcessorEndpointFullTypeStr == typeFullName && ga.HiddenData == hd);
  406. dbContext.GenericAlarms.RemoveRange(deleting);
  407. var effectRowCount = await dbContext.SaveChangesAsync();
  408. }
  409. var r = await this.FireGenericAlarm(source, newAlarm, true, newAlarmHiddenDataSelector(newAlarm), muteNewAlarmFiring);
  410. return r;
  411. }
  412. finally
  413. {
  414. if (lockTaken) this.spinLock_GuardTwoStageOperation.Exit(false);
  415. }
  416. }
  417. /// <summary>
  418. /// Try firing an alarm by only there's no matched opened alarm in db.
  419. /// </summary>
  420. /// <param name="source"></param>
  421. /// <param name="newAlarm"></param>
  422. /// <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>
  423. /// <param name="newAlarmHiddenDataSelector"></param>
  424. /// <returns></returns>
  425. public async Task<int?> FirePersistGenericAlarmIfNotExists(IProcessor source, GenericAlarm newAlarm,
  426. Func<GenericAlarm, string> existsAlarmsHiddenDataPredicate,
  427. Func<GenericAlarm, string> newAlarmHiddenDataSelector)
  428. {
  429. if (source == null) throw new ArgumentNullException(nameof(source));
  430. bool lockTaken = false;
  431. try
  432. {
  433. this.spinLock_GuardTwoStageOperation.Enter(ref lockTaken);
  434. using (var scope = this.services.CreateScope())
  435. {
  436. var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
  437. var hd = existsAlarmsHiddenDataPredicate(newAlarm);
  438. var exist = await this.GetPersistGenericAlarms(source, null, null, null, hd, null, null, null, true, false, 0, 1);
  439. if (exist.Any())
  440. return exist.First().Id;
  441. var r = await this.FireGenericAlarm(source, newAlarm, true, newAlarmHiddenDataSelector(newAlarm));
  442. return r;
  443. }
  444. }
  445. finally
  446. {
  447. if (lockTaken) this.spinLock_GuardTwoStageOperation.Exit(false);
  448. }
  449. }
  450. }
  451. public class UniversalGenericAlarmEventFiredEventArg : System.EventArgs
  452. {
  453. public IProcessor Originator { get; }
  454. public GenericAlarm GenericAlarm { get; }
  455. public int? PersistId { get; }
  456. public UniversalGenericAlarmEventFiredEventArg(IProcessor originator, GenericAlarm genericAlarm, int? persistId)
  457. {
  458. this.Originator = originator;
  459. this.GenericAlarm = genericAlarm;
  460. this.PersistId = persistId;
  461. }
  462. }
  463. }