123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- using AutoMapper;
- using Edge.Core.Configuration;
- using Edge.Core.Database;
- using Edge.Core.Database.Models;
- using Edge.Core.Processor;
- using Edge.Core.Processor.Dispatcher.Attributes;
- using Edge.Core.UniversalApi.Auditing;
- using Microsoft.Extensions.DependencyInjection;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Reflection;
- using System.Runtime.CompilerServices;
- using System.Text.Json;
- using System.Threading;
- using System.Threading.Tasks;
- namespace Edge.Core.UniversalApi
- {
- public class UniversalApiHub
- {
- private IEnumerable<IProcessor> processors;
- private IEnumerable<IAuditingStore> auditingStores;
- private IServiceProvider services;
- private bool config_DisableApiAuditing = false;
- private bool config_EnableApiInputAndOutputAuditing = false;
- public event EventHandler<UniversalGenericAlarmEventFiredEventArg> OnUniversalGenericAlarmEventFired;
- public async Task InitAsync(IEnumerable<IProcessor> processors)
- {
- this.processors = processors;
- foreach (var p in this.CommunicationProviders)
- {
- await p.SetupAsync(this.processors);
- }
- }
- public IEnumerable<ICommunicationProvider> CommunicationProviders { get; }
- public UniversalApiHub(IEnumerable<ICommunicationProvider> communicationProviders, IServiceProvider services)
- {
- this.CommunicationProviders = communicationProviders;
- this.services = services;
- var configurator = services.GetService<Configurator>();
- this.config_DisableApiAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
- ?.FirstOrDefault(p => p.Name.Equals("disableUniversalApiAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
- this.config_EnableApiInputAndOutputAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
- ?.FirstOrDefault(p => p.Name.Equals("enableUniversalApiInputAndOutputAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
- this.auditingStores = services.GetRequiredService<IEnumerable<IAuditingStore>>();
- configurator.OnConfigFileChanged += (_, __) =>
- {
- this.config_DisableApiAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
- ?.FirstOrDefault(p => p.Name.Equals("disableUniversalApiAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
- this.config_EnableApiInputAndOutputAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
- ?.FirstOrDefault(p => p.Name.Equals("enableUniversalApiInputAndOutputAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
- };
- }
- /// <summary>
- /// Fire an event to all underlying CommunicationProviders.
- /// Make sure class Attribute of `Event UniversalApi` declared in App or DeviceHandler.
- /// </summary>
- /// <param name="source"></param>
- /// <param name="eventName"></param>
- /// <param name="eventData"></param>
- /// <returns></returns>
- public async Task FireEvent(IProcessor source, string eventName, object eventData)
- {
- #region
- AuditLogInfo auditLogInfo = null;
- if (!this.config_DisableApiAuditing)
- {
- auditLogInfo = new AuditLogInfo()
- {
- ClientIdentity = "",
- DeviceHandlerOrAppName = source.SelectHandlerOrAppThenCast<object>().GetType().FullName,
- ExecutionTime = DateTime.Now,
- };
- auditLogInfo.Actions.Add(new AuditLogActionInfo()
- {
- ApiName = "event : " + eventName,
- InputParameters =
- this.config_EnableApiInputAndOutputAuditing ?
- JsonSerializer.Serialize(eventData) : null,
- });
- auditLogInfo.Prepare();
- }
- #endregion
- try
- {
- if (this.CommunicationProviders == null) return;
- foreach (var provider in this.CommunicationProviders)
- {
- try
- {
- var executeResult = await provider.RouteEventAsync(source,
- new EventDescriptor() { Name = eventName, Data = eventData });
- }
- catch (Exception exxx)
- {
- #region
- //if (!this.config_DisableApiAuditing)
- //{ auditLogInfo.CommitWithExceptions(new Exception[] { exxx }); }
- #endregion
- }
- }
- }
- finally
- {
- if (!this.config_DisableApiAuditing)
- {
- auditLogInfo.CommitWithExeResult(true);
- Task.WaitAll(this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray());
- }
- }
- }
- /// <summary>
- /// Fire generic event without persist it(like fire and forget).
- /// the UI component would receive it at the moment and only for onetime, to showing a message to user.
- /// </summary>
- /// <param name="source"></param>
- /// <param name="genericAlarm"></param>
- /// <returns></returns>
- public Task FireGenericAlarm(IProcessor source, GenericAlarm genericAlarm)
- {
- return this.FireGenericAlarm(source, genericAlarm, false, null);
- }
- /// <summary>
- /// Fire generic event and persist it.
- /// the UI component would receive it at the moment and can retrieve it anytime later, to showing a message to user.
- /// </summary>
- /// <param name="source"></param>
- /// <param name="genericAlarm"></param>
- /// <param name="hiddenDataSelector">any custom data to persist together with this alarm, used for searching.</param>
- /// <returns>the persist record id, use it for retrieve back the record.</returns>
- public Task<int?> FirePersistGenericAlarm(IProcessor source, GenericAlarm genericAlarm, Func<GenericAlarm, string> hiddenDataSelector)
- {
- return this.FireGenericAlarm(source, genericAlarm, true, hiddenDataSelector(genericAlarm));
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="source"></param>
- /// <param name="genericAlarm"></param>
- /// <param name="persist">if true, the alarm will be persist</param>
- /// <param name="hiddenData"></param>
- /// <param name="muteFiring">not firing the event, used for scenario that refresh the persist alarm without re-firing it.</param>
- /// <returns></returns>
- private async Task<int?> FireGenericAlarm(IProcessor source, GenericAlarm genericAlarm, bool persist, string hiddenData, bool muteFiring = false)
- {
- int? persistAlarmDbRecordKey = null;
- if (!persist)
- {
- }
- else
- {
- var mapper = this.services.GetRequiredService<IMapper>();
- using (var scope = this.services.CreateScope())
- {
- }
- }
- if (!muteFiring)
- this.OnUniversalGenericAlarmEventFired?.Invoke(this, new UniversalGenericAlarmEventFiredEventArg(source, genericAlarm, persistAlarmDbRecordKey));
- //await this.FireEvent(source, GenericAlarm.UniversalApiEventName, genericAlarm);
- return persistAlarmDbRecordKey;
- }
- /// <summary>
- /// Mark several Opening state alarms to Closed state, and fill with close reason for each.
- /// </summary>
- /// <param name="alarmIdAndCloseReasons">list of opening state alarm id and close reason.</param>
- /// <returns></returns>
- public async Task ClosePersistGenericAlarms(IEnumerable<Tuple<int, string>> alarmIdAndCloseReasons)
- {
- using (var scope = this.services.CreateScope())
- {
- //await dbContext.SaveChangesAsync();
- }
- }
- public async Task AckPersistGenericAlarms(IEnumerable<Tuple<int, string>> alarmIdAndAckReasons, bool isForUnAck = false)
- {
- using (var scope = this.services.CreateScope())
- {
-
- // await dbContext.SaveChangesAsync();
- }
- }
- /// <summary>
- /// Mark all Opening state alarms to Closed state, and fill with the same close reason, for a specific processor.
- /// </summary>
- /// <param name="source">all alarms for this processor will be closed.</param>
- /// <param name="closeReason">all target alarms will set with this close reason</param>
- /// <returns></returns>
- public Task<int> ClosePersistGenericAlarms(IProcessor source, string closeReason)
- {
- return this.ClosePersistGenericAlarms(source, null, closeReason);
- }
- /// <summary>
- /// Mark all Opening state alarms to Closed state, and fill with the same close reason.
- /// </summary>
- /// <param name="source"></param>
- /// <param name="hiddenDataHint">the searching criteria, the hidden data fields contains this value will be matched.</param>
- /// <param name="closeReason">all target alarms will set with this close reason</param>
- /// <returns></returns>
- public async Task<int> ClosePersistGenericAlarms(IProcessor source, string hiddenDataHint, string closeReason)
- {
- var originator = source.ProcessorDescriptor()?.DeviceHandlerOrApp?.GetType()
- ?.GetCustomAttributes<MetaPartsDescriptor>()?.FirstOrDefault()?.DisplayName;
- if (string.IsNullOrEmpty(originator))
- originator = source.ProcessorDescriptor()?.DeviceHandlerOrApp?.GetType().FullName;
- using (var scope = this.services.CreateScope())
- {
-
- //var effectRowCount = await dbContext.SaveChangesAsync();
- return 1;
- }
- }
- /// <summary>
- /// Query the persist generic alarms by search conditions.
- /// </summary>
- /// <param name="source">only alarms from this processor are returned, leave null to ignore this constrait.</param>
- /// <param name="originators">null to ignore this constrait.</param>
- /// <param name="alarmCategories">null to ignore this constrait.</param>
- /// <param name="alarmSubCategories">null to ignore this constrait.</param>
- /// <param name="alarmHiddenData">null to ignore this constrait.</param>
- /// <param name="alarmOpeningDateTimeFrom">null to ignore this constrait.</param>
- /// <param name="alarmOpeningDateTimeTo">null to ignore this constrait.</param>
- /// <param name="severity">null to ignore this constrait.</param>
- /// <param name="includeAckedStateAlarm"></param>
- /// <param name="includeClosedStateAlarm"></param>
- /// <param name="pageIndex"></param>
- /// <param name="pageRowCount"></param>
- /// <returns></returns>
- public async Task<IEnumerable<GenericAlarmDbModel>> GetPersistGenericAlarms(
- IProcessor source,
- string[] originators,
- string[] alarmCategories,
- string[] alarmSubCategories,
- string alarmHiddenData,
- DateTime? alarmOpeningDateTimeFrom, DateTime? alarmOpeningDateTimeTo,
- GenericAlarmSeverity? severity,
- bool includeAckedStateAlarm,
- bool includeClosedStateAlarm,
- int pageIndex, int pageRowCount)
- {
- using (var scope = this.services.CreateScope())
- {
- return null;
- //return r.Select(d => new
- //{
- // d.Id,
- // d.Originator,
- // d.Severity,
- // d.Category,
- // d.SubCategory,
- // d.Title,
- // d.Detail,
- // d.ClosedTimestamp,
- // d.ClosedReason,
- // d.Action,
- //});
- }
- }
- /// <summary>
- /// Close the alarms by match its hidden data, and create a new persist generic alarm.
- /// </summary>
- /// <param name="source"></param>
- /// <param name="closingAlarmsHiddenDataPredicate">predictor for hidden data match for test with all exist alarms, the matched ones will be all closed.</param>
- /// <param name="closeReason"></param>
- /// <param name="newAlarm"></param>
- /// <param name="newAlarmHiddenDataSelector">generator for hidden data that will be used for create a new alarm.</param>
- /// <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>
- /// <returns></returns>
- public async Task<int?> CloseAndFirePersistGenericAlarm(IProcessor source, GenericAlarm newAlarm,
- Func<GenericAlarm, string> closingAlarmsHiddenDataPredicate, string closeReason,
- Func<GenericAlarm, string> newAlarmHiddenDataSelector, bool muteNewAlarmFiring = false)
- {
- if (source == null) throw new ArgumentNullException(nameof(source));
- bool lockTaken = false;
- try
- {
- this.spinLock_GuardTwoStageOperation.Enter(ref lockTaken);
- var alarmsForClosing = await GetPersistGenericAlarms(source, null, null, null, closingAlarmsHiddenDataPredicate(newAlarm), null, null, null, true, false, 0, 100);
- if (alarmsForClosing.Any())
- await this.ClosePersistGenericAlarms(alarmsForClosing.Select(c => new Tuple<int, string>(c.Id, closeReason)));
- var r = await this.FireGenericAlarm(source, newAlarm, true, newAlarmHiddenDataSelector(newAlarm), muteNewAlarmFiring);
- return r;
- }
- finally
- {
- if (lockTaken) this.spinLock_GuardTwoStageOperation.Exit(false);
- }
- }
- private SpinLock spinLock_GuardTwoStageOperation = new SpinLock();
- /// <summary>
- /// Clear(remove from db) the alarms by match its hidden data, and create a new persist generic alarm.
- /// </summary>
- /// <param name="source"></param>
- /// <param name="newAlarm"></param>
- /// <param name="clearingAlarmsHiddenDataPredicate">predictor for hidden data match for test with all exist alarms, the matched ones will be all cleared.</param>
- /// <param name="newAlarmHiddenDataSelector"></param>
- /// <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>
- /// <returns></returns>
- public async Task<int?> ClearAndFirePersistGenericAlarm(IProcessor source, GenericAlarm newAlarm,
- Func<GenericAlarm, string> clearingAlarmsHiddenDataPredicate,
- Func<GenericAlarm, string> newAlarmHiddenDataSelector, bool muteNewAlarmFiring = false)
- {
- if (source == null) throw new ArgumentNullException(nameof(source));
- bool lockTaken = false;
- try
- {
- this.spinLock_GuardTwoStageOperation.Enter(ref lockTaken);
- using (var scope = this.services.CreateScope())
- {
-
- }
- var r = await this.FireGenericAlarm(source, newAlarm, true, newAlarmHiddenDataSelector(newAlarm), muteNewAlarmFiring);
- return r;
- }
- finally
- {
- if (lockTaken) this.spinLock_GuardTwoStageOperation.Exit(false);
- }
- }
- /// <summary>
- /// Try firing an alarm by only there's no matched opened alarm in db.
- /// </summary>
- /// <param name="source"></param>
- /// <param name="newAlarm"></param>
- /// <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>
- /// <param name="newAlarmHiddenDataSelector"></param>
- /// <returns></returns>
- public async Task<int?> FirePersistGenericAlarmIfNotExists(IProcessor source, GenericAlarm newAlarm,
- Func<GenericAlarm, string> existsAlarmsHiddenDataPredicate,
- Func<GenericAlarm, string> newAlarmHiddenDataSelector)
- {
- if (source == null) throw new ArgumentNullException(nameof(source));
- bool lockTaken = false;
- try
- {
- this.spinLock_GuardTwoStageOperation.Enter(ref lockTaken);
- using (var scope = this.services.CreateScope())
- {
-
- return null;
- }
- }
- finally
- {
- if (lockTaken) this.spinLock_GuardTwoStageOperation.Exit(false);
- }
- }
- }
- public class UniversalGenericAlarmEventFiredEventArg : System.EventArgs
- {
- public IProcessor Originator { get; }
- public GenericAlarm GenericAlarm { get; }
- public int? PersistId { get; }
- public UniversalGenericAlarmEventFiredEventArg(IProcessor originator, GenericAlarm genericAlarm, int? persistId)
- {
- this.Originator = originator;
- this.GenericAlarm = genericAlarm;
- this.PersistId = persistId;
- }
- }
- }
|