123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587 |
- using Comal.Classes;
- using Expressive;
- using InABox.Core;
- using InABox.Database;
- using Newtonsoft.Json;
- using Newtonsoft.Json.Serialization;
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using System.Reflection;
- using System.Text;
- using System.Threading.Tasks;
- namespace PRS.Shared.Events;
- public interface IEventData
- {
- IEvent Event { get; }
- IEnumerable<string> ReferencedVariables { get; }
- }
- public class EventData<T, TDataModel> : IEventData
- where T : IEvent<TDataModel>
- where TDataModel : IEventDataModel
- {
- public T Event { get; set; }
- /// <summary>
- /// A list of triggers for this event. If any of the triggers match, the event runs.
- /// </summary>
- public List<IEventTrigger<T, TDataModel>> Triggers { get; set; }
- public List<IEventAction<T>> Actions { get; set; }
- IEvent IEventData.Event => Event;
- public IEnumerable<string> ReferencedVariables =>
- Triggers.SelectMany(x => x.ReferencedVariables)
- .Concat(Actions.SelectMany(x => x.ReferencedVariables))
- .Distinct();
- public EventData(T eventData)
- {
- Event = eventData;
- Triggers = new List<IEventTrigger<T, TDataModel>>();
- Actions = new List<IEventAction<T>>();
- }
- public Notification GenerateNotification(TDataModel model) => Event.GenerateNotification(model);
- }
- public static class EventUtils
- {
- #region Serialisation
- private class WritablePropertiesOnlyResolver : DefaultContractResolver
- {
- protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
- {
- IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
- return props.Where(p => p.Writable).ToList();
- }
- }
- private static JsonSerializerSettings SerializationSettings()
- {
- var settings = Serialization.CreateSerializerSettings();
- settings.TypeNameHandling = TypeNameHandling.Auto;
- settings.ContractResolver = new WritablePropertiesOnlyResolver();
- return settings;
- }
- public static string Serialize(IEventData data)
- {
- return JsonConvert.SerializeObject(data, typeof(IEventData), SerializationSettings());
- }
- public static IEventData Deserialize(string json)
- {
- return JsonConvert.DeserializeObject<IEventData>(json, SerializationSettings())!;
- }
- #endregion
- #region Event Types
- private static Dictionary<string, Type>? _eventTypes;
- private static Dictionary<string, Type>? _triggerTypes;
- private static Dictionary<string, Type>? _actionTypes;
- private static Dictionary<Type, List<Type>>? _eventTriggerTypes;
- private static Dictionary<Type, List<Type>>? _eventActionTypes;
- [MemberNotNullWhen(true, nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes), nameof(_eventTriggerTypes), nameof(_eventActionTypes))]
- private static bool _loadedTypes { get; set; }
- [MemberNotNull(nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes), nameof(_eventTriggerTypes), nameof(_eventActionTypes))]
- private static void LoadTypes()
- {
- if (_loadedTypes) return;
- _loadedTypes = true;
- _eventTypes = new();
- _triggerTypes = new();
- _actionTypes = new();
- _eventTriggerTypes = new();
- _eventActionTypes = new();
- foreach(var type in CoreUtils.TypeList(x => true))
- {
- if (type.HasInterface(typeof(IEvent<>)))
- {
- if (type.GetConstructor([]) is not null)
- {
- _eventTypes[type.Name] = type;
- }
- }
- else if (type.GetInterfaceDefinition(typeof(IEventTrigger<,>)) is Type eventTriggerInterface)
- {
- if (type.GetConstructor([]) is not null)
- {
- _triggerTypes[type.Name] = type;
- var eventType = eventTriggerInterface.GenericTypeArguments[0];
- eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
- _eventTriggerTypes.GetValueOrAdd(eventType).Add(type);
- }
- }
- else if (type.GetInterfaceDefinition(typeof(IEventAction<>)) is Type eventActionInterface)
- {
- if (type.GetConstructor([]) is not null)
- {
- _actionTypes[type.Name] = type;
- var eventType = eventActionInterface.GenericTypeArguments[0];
- eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
- _eventActionTypes.GetValueOrAdd(eventType).Add(type);
- }
- }
- }
- }
- public static IEnumerable<Type> GetEventTriggerTypes(Type eventType)
- {
- LoadTypes();
- eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
- return _eventTriggerTypes.GetValueOrDefault(eventType) ?? Enumerable.Empty<Type>();
- }
- public static IEnumerable<Type> GetEventActionTypes(Type eventType)
- {
- LoadTypes();
- eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
- return _eventActionTypes.GetValueOrDefault(eventType) ?? Enumerable.Empty<Type>();
- }
- public static Type GetEventType(string type)
- {
- LoadTypes();
- return _eventTypes[type]; // Force exception if not a valid type name.
- }
- public static Type GetTriggerType(string type)
- {
- LoadTypes();
- return _triggerTypes[type]; // Force exception if not a valid type name.
- }
- public static Type GetActionType(string type)
- {
- LoadTypes();
- return _actionTypes[type]; // Force exception if not a valid type name.
- }
- #endregion
- #region Execution
- private static bool Check<T, TDataModel>(EventData<T, TDataModel> ev, TDataModel dataModel)
- where T : IEvent<TDataModel>
- where TDataModel : IEventDataModel
- {
- return ev.Triggers.Any(x => x.Check(dataModel));
- }
- public static void Run<T, TDataModel>(IStore store, Event ev, EventData<T, TDataModel> evData, TDataModel dataModel)
- where T : IEvent<TDataModel>
- where TDataModel : IEventDataModel
- {
- if (!Check(evData, dataModel)) return;
- foreach(var action in evData.Actions)
- {
- IEvent.RunAction(evData.Event, dataModel, action);
- }
- NotifySubscribers(store, ev, evData, dataModel);
- }
- private static void NotifySubscribers<T, TDataModel>(IStore store, Event ev, EventData<T, TDataModel> evData, TDataModel dataModel)
- where T : IEvent<TDataModel>
- where TDataModel : IEventDataModel
- {
- string? description;
- if (ev.NotificationExpression.IsNullOrWhiteSpace())
- {
- description = null;
- }
- else
- {
- var descriptionExpr = new CoreExpression(ev.NotificationExpression, typeof(string));
- if(!descriptionExpr.TryEvaluate<string>(dataModel).Get(out description, out var error))
- {
- CoreUtils.LogException(store.UserID, error, extra: "Error notifying subscribers", store.Logger);
- return;
- }
- }
- var subscribers = store.Provider.Query(
- new Filter<EventSubscriber>(x => x.Event.ID).IsEqualTo(ev.ID),
- Columns.None<EventSubscriber>().Add(x => x.Employee.ID))
- .ToArray<EventSubscriber>();
- var notifications = new List<Notification>();
- foreach(var subscriber in subscribers)
- {
- var notification = evData.GenerateNotification(dataModel);
- notification.Employee.CopyFrom(subscriber.Employee);
- if(description is not null)
- {
- notification.Description = description;
- }
- notifications.Add(notification);
- }
- store.Provider.Save(notifications);
- }
- #endregion
- #region Event Cache
- private static bool _loadedCache = false;
- private static Dictionary<EventType, Dictionary<Type, List<(Event Event, IEventData EventData)>>> _entityEvents = new();
- private static Dictionary<Guid, (EventType, Type)> _entityEventMap = new();
- public static void ReloadCache(IProvider provider)
- {
- _loadedCache = true;
- _entityEvents.Clear();
- _entityEventMap.Clear();
- var events = provider.Query(
- new Filter<Event>(x => x.Enabled).IsEqualTo(true),
- Columns.None<Event>().Add(x => x.ID).Add(x => x.Code).Add(x => x.EventType).Add(x => x.Data))
- .ToObjects<Event>();
- foreach(var ev in events)
- {
- AddEvent(provider, ev);
- }
- }
- private static void AddEntityEvent(Event ev)
- {
- if(ev.Data is not null && ev.Data.Length != 0)
- {
- var data = Deserialize(ev.Data);
- var entityType = data.Event.GetType().GenericTypeArguments[0];
- var list = _entityEvents.GetValueOrAdd(ev.EventType).GetValueOrAdd(entityType);
- list.RemoveAll(x => x.Event.ID == ev.ID);
-
- list.Add((ev, data));
- _entityEventMap[ev.ID] = (ev.EventType, entityType);
- }
- }
- private static void RemoveEntityEvent(Event ev)
- {
- if(_entityEventMap.TryGetValue(ev.ID, out var item))
- {
- if(_entityEvents.TryGetValue(item.Item1, out var eventTypeEvents))
- {
- if(eventTypeEvents.TryGetValue(item.Item2, out var entityEvents))
- {
- entityEvents.RemoveAll(x => x.Event.ID == ev.ID);
- if(entityEvents.Count == 0)
- {
- eventTypeEvents.Remove(item.Item2);
- }
- }
- if(eventTypeEvents.Count == 0)
- {
- _entityEvents.Remove(item.Item1);
- }
- }
- _entityEventMap.Remove(ev.ID);
- }
- }
- public static void RemoveEvent(IProvider provider, Event ev)
- {
- if (!_loadedCache) ReloadCache(provider);
- switch (ev.EventType)
- {
- case EventType.AfterSave:
- RemoveEntityEvent(ev);
- break;
- }
- }
- public static void AddEvent(IProvider provider, Event ev)
- {
- if (!_loadedCache) ReloadCache(provider);
- switch (ev.EventType)
- {
- case EventType.AfterSave:
- AddEntityEvent(ev);
- break;
- }
- }
- #endregion
- #region Public Event Running Interface
- public static void AfterSave<T>(IStore store, T entity)
- where T : Entity, new()
- {
- if (!_loadedCache) ReloadCache(store.Provider);
- var events = _entityEvents.GetValueOrDefault(EventType.AfterSave)
- ?.GetValueOrDefault(typeof(T));
- if (events is null) return;
- foreach(var ev in events)
- {
- var eventData = (ev.EventData as EventData<SaveEvent<T>, SaveEventDataModel<T>>)!;
- var model = new SaveEventDataModel<T>(entity, store);
- eventData.Event.Init(store, eventData, model);
- Run(store, ev.Event, eventData, model);
- }
- }
- #endregion
- }
- #region DataModel Definition
- public interface IEventVariable
- {
- string Name { get; set; }
- Type Type { get; set; }
- }
- public class StandardEventVariable : IEventVariable
- {
- public string Name { get; set; }
- public Type Type { get; set; }
- public StandardEventVariable(string name, Type type)
- {
- Name = name;
- Type = type;
- }
- }
- public class ListEventVariable : IEventVariable
- {
- public string Name { get; set; }
- public Type Type { get; set; }
- public ListEventVariable(string name, Type type)
- {
- Name = name;
- Type = type;
- }
- }
- public interface IEventDataModelDefinition
- {
- IEnumerable<IEventVariable> GetVariables();
- IEventVariable? GetVariable(string name);
- }
- #endregion
- #region DataModel
- public interface IEventDataModel : IVariableProvider
- {
- bool TryGetVariable(string name, out object? value);
- bool IVariableProvider.TryGetValue(string variableName, out object? value)
- {
- return TryGetVariable(variableName, out value);
- }
- T RootModel<T>()
- where T : IEventDataModel
- {
- if(this is IChildEventDataModel child)
- {
- return child.Parent.RootModel<T>();
- }
- else if(this is T root)
- {
- return root;
- }
- else
- {
- throw new Exception($"Root model of wrong type; expected {typeof(T).FullName}; got {GetType().FullName}");
- }
- }
- }
- public interface ITypedEventDataModel : IEventDataModel
- {
- public Type EntityType { get; }
- }
- public interface IChildEventDataModel : IEventDataModel
- {
- IEventDataModel Parent { get; }
- }
- public class ChildEventDataModel : IChildEventDataModel
- {
- public IEventDataModel Parent { get; set; }
- public Dictionary<string, object?> Values { get; set; } = new Dictionary<string, object?>();
- public ChildEventDataModel(IEventDataModel parent)
- {
- Parent = parent;
- }
- public bool TryGetVariable(string name, out object? value)
- {
- return Values.TryGetValue(name, out value)
- || Parent.TryGetVariable(name, out value);
- }
- public void Init(IEventData data)
- {
- }
- }
- #endregion
- public interface IEvent
- {
- IEventDataModelDefinition DataModelDefinition();
- public static void RunAction(IEvent ev, IEventDataModel model, IEventAction action)
- {
- var dataModelDef = ev.DataModelDefinition();
- var values = new List<(string name, object? value)[]>() { Array.Empty<(string, object?)>() };
- foreach(var variable in action.ReferencedVariables)
- {
- var varDef = dataModelDef.GetVariable(variable);
- if(varDef is ListEventVariable)
- {
- if (model.TryGetVariable(varDef.Name, out var list) && list is IEnumerable enumerable)
- {
- var oldValues = values;
- values = new List<(string name, object? value)[]>();
- foreach(var item in enumerable)
- {
- foreach(var valueList in oldValues)
- {
- values.Add(valueList.Concatenate(new (string name, object? value)[]
- {
- (varDef.Name, item)
- }));
- }
- }
- }
- else
- {
- values.Clear();
- break;
- }
- }
- }
- if(values.Count > 0 && (values.Count > 1 || values[0].Length > 0))
- {
- var subModel = new ChildEventDataModel(model);
- foreach(var valueSet in values)
- {
- subModel.Values.Clear();
- foreach(var (name, value) in valueSet)
- {
- subModel.Values[name] = value;
- }
- action.Execute(subModel);
- }
- }
- else
- {
- action.Execute(model);
- }
- }
- }
- public interface IEvent<TDataModel> : IEvent
- {
- void Init(IStore store, IEventData data, TDataModel model);
- Notification GenerateNotification(TDataModel model);
- }
- public interface IEventTrigger
- {
- IEnumerable<string> ReferencedVariables { get; }
- string Description { get; }
- }
- public interface IEventTrigger<TEvent, TDataModel> : IEventTrigger
- where TEvent : IEvent<TDataModel>
- where TDataModel : IEventDataModel
- {
- bool Check(TDataModel dataModel);
- }
- public interface IEventTriggerParent<TEvent, TDataModel> : IEventTrigger<TEvent, TDataModel>
- where TEvent : IEvent<TDataModel>
- where TDataModel : IEventDataModel
- {
- public List<IEventTrigger<TEvent, TDataModel>> ChildTriggers { get; }
- }
- public class AndTrigger<TEvent, TDataModel> : IEventTrigger<TEvent, TDataModel>, IEventTriggerParent<TEvent, TDataModel>
- where TEvent : IEvent<TDataModel>
- where TDataModel : IEventDataModel
- {
- public List<IEventTrigger<TEvent, TDataModel>> ChildTriggers { get; set; } = new();
- public IEnumerable<string> ReferencedVariables => ChildTriggers.SelectMany(x => x.ReferencedVariables);
- public string Description => "And";
- public bool Check(TDataModel dataModel)
- {
- return ChildTriggers.All(x => x.Check(dataModel));
- }
- }
- public class OrTrigger<TEvent, TDataModel> : IEventTrigger<TEvent, TDataModel>, IEventTriggerParent<TEvent, TDataModel>
- where TEvent : IEvent<TDataModel>
- where TDataModel : IEventDataModel
- {
- public List<IEventTrigger<TEvent, TDataModel>> ChildTriggers { get; set; } = new();
- public IEnumerable<string> ReferencedVariables => ChildTriggers.SelectMany(x => x.ReferencedVariables);
- public string Description => "Or";
- public bool Check(TDataModel dataModel)
- {
- return ChildTriggers.Any(x => x.Check(dataModel));
- }
- }
- public interface IEventAction
- {
- IEnumerable<string> ReferencedVariables { get; }
- object? Execute(IEventDataModel dataModel);
- string Description { get; }
- }
- public interface IEventAction<TEvent> : IEventAction
- where TEvent : IEvent
- {
- }
|