Event.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. using Comal.Classes;
  2. using Expressive;
  3. using InABox.Core;
  4. using InABox.Database;
  5. using Newtonsoft.Json;
  6. using Newtonsoft.Json.Serialization;
  7. using System;
  8. using System.Collections;
  9. using System.Collections.Generic;
  10. using System.Diagnostics.CodeAnalysis;
  11. using System.Linq;
  12. using System.Reflection;
  13. using System.Text;
  14. using System.Threading.Tasks;
  15. namespace PRS.Shared.Events;
  16. public interface IEventData
  17. {
  18. IEvent Event { get; }
  19. IEnumerable<string> ReferencedVariables { get; }
  20. }
  21. public class EventData<T, TDataModel> : IEventData
  22. where T : IEvent<TDataModel>
  23. where TDataModel : IEventDataModel
  24. {
  25. public T Event { get; set; }
  26. /// <summary>
  27. /// A list of triggers for this event. If any of the triggers match, the event runs.
  28. /// </summary>
  29. public List<IEventTrigger<T, TDataModel>> Triggers { get; set; }
  30. public List<IEventAction<T>> Actions { get; set; }
  31. IEvent IEventData.Event => Event;
  32. public IEnumerable<string> ReferencedVariables =>
  33. Triggers.SelectMany(x => x.ReferencedVariables)
  34. .Concat(Actions.SelectMany(x => x.ReferencedVariables))
  35. .Distinct();
  36. public EventData(T eventData)
  37. {
  38. Event = eventData;
  39. Triggers = new List<IEventTrigger<T, TDataModel>>();
  40. Actions = new List<IEventAction<T>>();
  41. }
  42. public Notification GenerateNotification(TDataModel model) => Event.GenerateNotification(model);
  43. }
  44. public static class EventUtils
  45. {
  46. #region Serialisation
  47. private class WritablePropertiesOnlyResolver : DefaultContractResolver
  48. {
  49. protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
  50. {
  51. IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
  52. return props.Where(p => p.Writable).ToList();
  53. }
  54. }
  55. private static JsonSerializerSettings SerializationSettings()
  56. {
  57. var settings = Serialization.CreateSerializerSettings();
  58. settings.TypeNameHandling = TypeNameHandling.Auto;
  59. settings.ContractResolver = new WritablePropertiesOnlyResolver();
  60. return settings;
  61. }
  62. public static string Serialize(IEventData data)
  63. {
  64. return JsonConvert.SerializeObject(data, typeof(IEventData), SerializationSettings());
  65. }
  66. public static IEventData Deserialize(string json)
  67. {
  68. return JsonConvert.DeserializeObject<IEventData>(json, SerializationSettings())!;
  69. }
  70. #endregion
  71. #region Event Types
  72. private static Dictionary<string, Type>? _eventTypes;
  73. private static Dictionary<string, Type>? _triggerTypes;
  74. private static Dictionary<string, Type>? _actionTypes;
  75. private static Dictionary<Type, List<Type>>? _eventTriggerTypes;
  76. private static Dictionary<Type, List<Type>>? _eventActionTypes;
  77. [MemberNotNullWhen(true, nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes), nameof(_eventTriggerTypes), nameof(_eventActionTypes))]
  78. private static bool _loadedTypes { get; set; }
  79. [MemberNotNull(nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes), nameof(_eventTriggerTypes), nameof(_eventActionTypes))]
  80. private static void LoadTypes()
  81. {
  82. if (_loadedTypes) return;
  83. _loadedTypes = true;
  84. _eventTypes = new();
  85. _triggerTypes = new();
  86. _actionTypes = new();
  87. _eventTriggerTypes = new();
  88. _eventActionTypes = new();
  89. foreach(var type in CoreUtils.TypeList(x => true))
  90. {
  91. if (type.HasInterface(typeof(IEvent<>)))
  92. {
  93. if (type.GetConstructor([]) is not null)
  94. {
  95. _eventTypes[type.Name] = type;
  96. }
  97. }
  98. else if (type.GetInterfaceDefinition(typeof(IEventTrigger<,>)) is Type eventTriggerInterface)
  99. {
  100. if (type.GetConstructor([]) is not null)
  101. {
  102. _triggerTypes[type.Name] = type;
  103. var eventType = eventTriggerInterface.GenericTypeArguments[0];
  104. eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
  105. _eventTriggerTypes.GetValueOrAdd(eventType).Add(type);
  106. }
  107. }
  108. else if (type.GetInterfaceDefinition(typeof(IEventAction<>)) is Type eventActionInterface)
  109. {
  110. if (type.GetConstructor([]) is not null)
  111. {
  112. _actionTypes[type.Name] = type;
  113. var eventType = eventActionInterface.GenericTypeArguments[0];
  114. eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
  115. _eventActionTypes.GetValueOrAdd(eventType).Add(type);
  116. }
  117. }
  118. }
  119. }
  120. public static IEnumerable<Type> GetEventTriggerTypes(Type eventType)
  121. {
  122. LoadTypes();
  123. eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
  124. return _eventTriggerTypes.GetValueOrDefault(eventType) ?? Enumerable.Empty<Type>();
  125. }
  126. public static IEnumerable<Type> GetEventActionTypes(Type eventType)
  127. {
  128. LoadTypes();
  129. eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
  130. return _eventActionTypes.GetValueOrDefault(eventType) ?? Enumerable.Empty<Type>();
  131. }
  132. public static Type GetEventType(string type)
  133. {
  134. LoadTypes();
  135. return _eventTypes[type]; // Force exception if not a valid type name.
  136. }
  137. public static Type GetTriggerType(string type)
  138. {
  139. LoadTypes();
  140. return _triggerTypes[type]; // Force exception if not a valid type name.
  141. }
  142. public static Type GetActionType(string type)
  143. {
  144. LoadTypes();
  145. return _actionTypes[type]; // Force exception if not a valid type name.
  146. }
  147. #endregion
  148. #region Execution
  149. private static bool Check<T, TDataModel>(EventData<T, TDataModel> ev, TDataModel dataModel)
  150. where T : IEvent<TDataModel>
  151. where TDataModel : IEventDataModel
  152. {
  153. return ev.Triggers.Any(x => x.Check(dataModel));
  154. }
  155. public static void Run<T, TDataModel>(IStore store, Event ev, EventData<T, TDataModel> evData, TDataModel dataModel)
  156. where T : IEvent<TDataModel>
  157. where TDataModel : IEventDataModel
  158. {
  159. if (!Check(evData, dataModel)) return;
  160. foreach(var action in evData.Actions)
  161. {
  162. IEvent.RunAction(evData.Event, dataModel, action);
  163. }
  164. NotifySubscribers(store, ev, evData, dataModel);
  165. }
  166. private static void NotifySubscribers<T, TDataModel>(IStore store, Event ev, EventData<T, TDataModel> evData, TDataModel dataModel)
  167. where T : IEvent<TDataModel>
  168. where TDataModel : IEventDataModel
  169. {
  170. string? description;
  171. if (ev.NotificationExpression.IsNullOrWhiteSpace())
  172. {
  173. description = null;
  174. }
  175. else
  176. {
  177. var descriptionExpr = new CoreExpression(ev.NotificationExpression, typeof(string));
  178. if(!descriptionExpr.TryEvaluate<string>(dataModel).Get(out description, out var error))
  179. {
  180. CoreUtils.LogException(store.UserID, error, extra: "Error notifying subscribers", store.Logger);
  181. return;
  182. }
  183. }
  184. var subscribers = store.Provider.Query(
  185. new Filter<EventSubscriber>(x => x.Event.ID).IsEqualTo(ev.ID),
  186. Columns.None<EventSubscriber>().Add(x => x.Employee.ID))
  187. .ToArray<EventSubscriber>();
  188. var notifications = new List<Notification>();
  189. foreach(var subscriber in subscribers)
  190. {
  191. var notification = evData.GenerateNotification(dataModel);
  192. notification.Employee.CopyFrom(subscriber.Employee);
  193. if(description is not null)
  194. {
  195. notification.Description = description;
  196. }
  197. notifications.Add(notification);
  198. }
  199. store.Provider.Save(notifications);
  200. }
  201. #endregion
  202. #region Event Cache
  203. private static bool _loadedCache = false;
  204. private static Dictionary<EventType, Dictionary<Type, List<(Event Event, IEventData EventData)>>> _entityEvents = new();
  205. private static Dictionary<Guid, (EventType, Type)> _entityEventMap = new();
  206. public static void ReloadCache(IProvider provider)
  207. {
  208. _loadedCache = true;
  209. _entityEvents.Clear();
  210. _entityEventMap.Clear();
  211. var events = provider.Query(
  212. new Filter<Event>(x => x.Enabled).IsEqualTo(true),
  213. Columns.None<Event>().Add(x => x.ID).Add(x => x.Code).Add(x => x.EventType).Add(x => x.Data))
  214. .ToObjects<Event>();
  215. foreach(var ev in events)
  216. {
  217. AddEvent(provider, ev);
  218. }
  219. }
  220. private static void AddEntityEvent(Event ev)
  221. {
  222. if(ev.Data is not null && ev.Data.Length != 0)
  223. {
  224. var data = Deserialize(ev.Data);
  225. var entityType = data.Event.GetType().GenericTypeArguments[0];
  226. var list = _entityEvents.GetValueOrAdd(ev.EventType).GetValueOrAdd(entityType);
  227. list.RemoveAll(x => x.Event.ID == ev.ID);
  228. list.Add((ev, data));
  229. _entityEventMap[ev.ID] = (ev.EventType, entityType);
  230. }
  231. }
  232. private static void RemoveEntityEvent(Event ev)
  233. {
  234. if(_entityEventMap.TryGetValue(ev.ID, out var item))
  235. {
  236. if(_entityEvents.TryGetValue(item.Item1, out var eventTypeEvents))
  237. {
  238. if(eventTypeEvents.TryGetValue(item.Item2, out var entityEvents))
  239. {
  240. entityEvents.RemoveAll(x => x.Event.ID == ev.ID);
  241. if(entityEvents.Count == 0)
  242. {
  243. eventTypeEvents.Remove(item.Item2);
  244. }
  245. }
  246. if(eventTypeEvents.Count == 0)
  247. {
  248. _entityEvents.Remove(item.Item1);
  249. }
  250. }
  251. _entityEventMap.Remove(ev.ID);
  252. }
  253. }
  254. public static void RemoveEvent(IProvider provider, Event ev)
  255. {
  256. if (!_loadedCache) ReloadCache(provider);
  257. switch (ev.EventType)
  258. {
  259. case EventType.AfterSave:
  260. RemoveEntityEvent(ev);
  261. break;
  262. }
  263. }
  264. public static void AddEvent(IProvider provider, Event ev)
  265. {
  266. if (!_loadedCache) ReloadCache(provider);
  267. switch (ev.EventType)
  268. {
  269. case EventType.AfterSave:
  270. AddEntityEvent(ev);
  271. break;
  272. }
  273. }
  274. #endregion
  275. #region Public Event Running Interface
  276. public static void AfterSave<T>(IStore store, T entity)
  277. where T : Entity, new()
  278. {
  279. if (!_loadedCache) ReloadCache(store.Provider);
  280. var events = _entityEvents.GetValueOrDefault(EventType.AfterSave)
  281. ?.GetValueOrDefault(typeof(T));
  282. if (events is null) return;
  283. foreach(var ev in events)
  284. {
  285. var eventData = (ev.EventData as EventData<SaveEvent<T>, SaveEventDataModel<T>>)!;
  286. var model = new SaveEventDataModel<T>(entity, store);
  287. eventData.Event.Init(store, eventData, model);
  288. Run(store, ev.Event, eventData, model);
  289. }
  290. }
  291. #endregion
  292. }
  293. #region DataModel Definition
  294. public interface IEventVariable
  295. {
  296. string Name { get; set; }
  297. Type Type { get; set; }
  298. }
  299. public class StandardEventVariable : IEventVariable
  300. {
  301. public string Name { get; set; }
  302. public Type Type { get; set; }
  303. public StandardEventVariable(string name, Type type)
  304. {
  305. Name = name;
  306. Type = type;
  307. }
  308. }
  309. public class ListEventVariable : IEventVariable
  310. {
  311. public string Name { get; set; }
  312. public Type Type { get; set; }
  313. public ListEventVariable(string name, Type type)
  314. {
  315. Name = name;
  316. Type = type;
  317. }
  318. }
  319. public interface IEventDataModelDefinition
  320. {
  321. IEnumerable<IEventVariable> GetVariables();
  322. IEventVariable? GetVariable(string name);
  323. }
  324. #endregion
  325. #region DataModel
  326. public interface IEventDataModel : IVariableProvider
  327. {
  328. bool TryGetVariable(string name, out object? value);
  329. bool IVariableProvider.TryGetValue(string variableName, out object? value)
  330. {
  331. return TryGetVariable(variableName, out value);
  332. }
  333. T RootModel<T>()
  334. where T : IEventDataModel
  335. {
  336. if(this is IChildEventDataModel child)
  337. {
  338. return child.Parent.RootModel<T>();
  339. }
  340. else if(this is T root)
  341. {
  342. return root;
  343. }
  344. else
  345. {
  346. throw new Exception($"Root model of wrong type; expected {typeof(T).FullName}; got {GetType().FullName}");
  347. }
  348. }
  349. }
  350. public interface ITypedEventDataModel : IEventDataModel
  351. {
  352. public Type EntityType { get; }
  353. }
  354. public interface IChildEventDataModel : IEventDataModel
  355. {
  356. IEventDataModel Parent { get; }
  357. }
  358. public class ChildEventDataModel : IChildEventDataModel
  359. {
  360. public IEventDataModel Parent { get; set; }
  361. public Dictionary<string, object?> Values { get; set; } = new Dictionary<string, object?>();
  362. public ChildEventDataModel(IEventDataModel parent)
  363. {
  364. Parent = parent;
  365. }
  366. public bool TryGetVariable(string name, out object? value)
  367. {
  368. return Values.TryGetValue(name, out value)
  369. || Parent.TryGetVariable(name, out value);
  370. }
  371. public void Init(IEventData data)
  372. {
  373. }
  374. }
  375. #endregion
  376. public interface IEvent
  377. {
  378. IEventDataModelDefinition DataModelDefinition();
  379. public static void RunAction(IEvent ev, IEventDataModel model, IEventAction action)
  380. {
  381. var dataModelDef = ev.DataModelDefinition();
  382. var values = new List<(string name, object? value)[]>() { Array.Empty<(string, object?)>() };
  383. foreach(var variable in action.ReferencedVariables)
  384. {
  385. var varDef = dataModelDef.GetVariable(variable);
  386. if(varDef is ListEventVariable)
  387. {
  388. if (model.TryGetVariable(varDef.Name, out var list) && list is IEnumerable enumerable)
  389. {
  390. var oldValues = values;
  391. values = new List<(string name, object? value)[]>();
  392. foreach(var item in enumerable)
  393. {
  394. foreach(var valueList in oldValues)
  395. {
  396. values.Add(valueList.Concatenate(new (string name, object? value)[]
  397. {
  398. (varDef.Name, item)
  399. }));
  400. }
  401. }
  402. }
  403. else
  404. {
  405. values.Clear();
  406. break;
  407. }
  408. }
  409. }
  410. if(values.Count > 0 && (values.Count > 1 || values[0].Length > 0))
  411. {
  412. var subModel = new ChildEventDataModel(model);
  413. foreach(var valueSet in values)
  414. {
  415. subModel.Values.Clear();
  416. foreach(var (name, value) in valueSet)
  417. {
  418. subModel.Values[name] = value;
  419. }
  420. action.Execute(subModel);
  421. }
  422. }
  423. else
  424. {
  425. action.Execute(model);
  426. }
  427. }
  428. }
  429. public interface IEvent<TDataModel> : IEvent
  430. {
  431. void Init(IStore store, IEventData data, TDataModel model);
  432. Notification GenerateNotification(TDataModel model);
  433. }
  434. public interface IEventTrigger
  435. {
  436. IEnumerable<string> ReferencedVariables { get; }
  437. string Description { get; }
  438. }
  439. public interface IEventTrigger<TEvent, TDataModel> : IEventTrigger
  440. where TEvent : IEvent<TDataModel>
  441. where TDataModel : IEventDataModel
  442. {
  443. bool Check(TDataModel dataModel);
  444. }
  445. public interface IEventTriggerParent<TEvent, TDataModel> : IEventTrigger<TEvent, TDataModel>
  446. where TEvent : IEvent<TDataModel>
  447. where TDataModel : IEventDataModel
  448. {
  449. public List<IEventTrigger<TEvent, TDataModel>> ChildTriggers { get; }
  450. }
  451. public class AndTrigger<TEvent, TDataModel> : IEventTrigger<TEvent, TDataModel>, IEventTriggerParent<TEvent, TDataModel>
  452. where TEvent : IEvent<TDataModel>
  453. where TDataModel : IEventDataModel
  454. {
  455. public List<IEventTrigger<TEvent, TDataModel>> ChildTriggers { get; set; } = new();
  456. public IEnumerable<string> ReferencedVariables => ChildTriggers.SelectMany(x => x.ReferencedVariables);
  457. public string Description => "And";
  458. public bool Check(TDataModel dataModel)
  459. {
  460. return ChildTriggers.All(x => x.Check(dataModel));
  461. }
  462. }
  463. public class OrTrigger<TEvent, TDataModel> : IEventTrigger<TEvent, TDataModel>, IEventTriggerParent<TEvent, TDataModel>
  464. where TEvent : IEvent<TDataModel>
  465. where TDataModel : IEventDataModel
  466. {
  467. public List<IEventTrigger<TEvent, TDataModel>> ChildTriggers { get; set; } = new();
  468. public IEnumerable<string> ReferencedVariables => ChildTriggers.SelectMany(x => x.ReferencedVariables);
  469. public string Description => "Or";
  470. public bool Check(TDataModel dataModel)
  471. {
  472. return ChildTriggers.Any(x => x.Check(dataModel));
  473. }
  474. }
  475. public interface IEventAction
  476. {
  477. IEnumerable<string> ReferencedVariables { get; }
  478. object? Execute(IEventDataModel dataModel);
  479. string Description { get; }
  480. }
  481. public interface IEventAction<TEvent> : IEventAction
  482. where TEvent : IEvent
  483. {
  484. }