SaveEvent.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. using Comal.Classes;
  2. using InABox.Core;
  3. using InABox.Database;
  4. using InABox.Scripting;
  5. using Newtonsoft.Json;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Diagnostics.CodeAnalysis;
  9. using System.Linq;
  10. using System.Text;
  11. namespace PRS.Shared.Events;
  12. public class SaveEvent<T> : IEvent<SaveEventDataModel<T>>
  13. where T : Entity, new()
  14. {
  15. public Type Entity => typeof(T);
  16. public IEventDataModelDefinition DataModelDefinition()
  17. {
  18. return new SaveEventDataModelDefinition<T>(this);
  19. }
  20. public Notification GenerateNotification(SaveEventDataModel<T> model)
  21. {
  22. var notification = new Notification();
  23. notification.Title = $"Updated {typeof(T).Name}";
  24. notification.Description = $"Updated {typeof(T).Name}";
  25. if(model.Entity.ID != Guid.Empty)
  26. {
  27. notification.EntityType = CoreUtils.EntityName(model.EntityType);
  28. notification.EntityID = model.Entity.ID;
  29. }
  30. return notification;
  31. }
  32. public void Init(IStore store, IEventData evData, SaveEventDataModel<T> model)
  33. {
  34. if (model.Entity.ID != Guid.Empty)
  35. {
  36. var loadCols = Columns.None<T>();
  37. var prefix = $"{typeof(T).Name}.";
  38. foreach (var variable in evData.ReferencedVariables)
  39. {
  40. if (variable.StartsWith(prefix))
  41. {
  42. var varName = variable[prefix.Length..];
  43. if (!model.Entity.HasOriginalValue(varName))
  44. {
  45. loadCols.Add(varName);
  46. }
  47. }
  48. }
  49. var data = store.Provider.Query(
  50. new Filter<T>(x => x.ID).IsEqualTo(model.Entity.ID),
  51. loadCols);
  52. if(data.Rows.Count > 0)
  53. {
  54. data.Rows[0].FillObject(model.Entity);
  55. }
  56. }
  57. }
  58. }
  59. public class SaveEventDataModelDefinition<T>(SaveEvent<T> ev) : IEventDataModelDefinition
  60. where T : Entity, new()
  61. {
  62. private IEventVariable[]? variables;
  63. public SaveEvent<T> Event { get; set; } = ev;
  64. public IEnumerable<IEventVariable> GetVariables()
  65. {
  66. if(variables is null)
  67. {
  68. variables = DatabaseSchema.AllProperties(Event.Entity).Select(x => new StandardEventVariable($"{typeof(T).Name}.{x.Name}", x.PropertyType)).ToArray();
  69. variables.SortBy(x => x.Name);
  70. }
  71. return variables;
  72. }
  73. public IEventVariable? GetVariable(string name)
  74. {
  75. if(variables is null)
  76. {
  77. var prefix = $"{typeof(T).Name}.";
  78. if (name.StartsWith(prefix))
  79. {
  80. name = name[prefix.Length..];
  81. var prop = DatabaseSchema.Property(Event.Entity, name);
  82. if(prop is null)
  83. {
  84. return null;
  85. }
  86. else
  87. {
  88. return new StandardEventVariable(prop.Name, prop.PropertyType);
  89. }
  90. }
  91. else
  92. {
  93. return null;
  94. }
  95. }
  96. else
  97. {
  98. return variables.FirstOrDefault(x => x.Name == name);
  99. }
  100. }
  101. }
  102. public class SaveEventDataModel<T>(T entity, IStore store) : IEventDataModel, ITypedEventDataModel
  103. where T : Entity, new()
  104. {
  105. public T Entity { get; set; } = entity;
  106. public Type EntityType => typeof(T);
  107. public IStore Store { get; } = store;
  108. public bool TryGetVariable(string name, out object? value)
  109. {
  110. var prefix = $"{typeof(T).Name}.";
  111. if (name.StartsWith(prefix))
  112. {
  113. name = name[prefix.Length..];
  114. var prop = DatabaseSchema.Property(typeof(T), name);
  115. if(prop != null)
  116. {
  117. value = prop.Getter()(Entity);
  118. return true;
  119. }
  120. }
  121. value = null;
  122. return false;
  123. }
  124. }
  125. #region Triggers
  126. [Caption("New Record")]
  127. public class CreatedSaveEventTrigger<T> : IEventTrigger<SaveEvent<T>, SaveEventDataModel<T>>
  128. where T : Entity, new()
  129. {
  130. public string Description => "New Record";
  131. public IEnumerable<string> ReferencedVariables => [];
  132. public bool Check(SaveEventDataModel<T> dataModel)
  133. {
  134. return dataModel.Entity.HasOriginalValue(x => x.ID);
  135. }
  136. }
  137. [Caption("Property Changed")]
  138. public class PropertyChangedSaveEventTrigger<T> : IEventTrigger<SaveEvent<T>, SaveEventDataModel<T>>
  139. where T : Entity, new()
  140. {
  141. [JsonIgnore]
  142. public IProperty? TriggerProperty { get; set; }
  143. [JsonProperty(PropertyName = "TriggerProperty")]
  144. private string? _property
  145. {
  146. get => TriggerProperty?.Name;
  147. set
  148. {
  149. TriggerProperty = value is null ? null : DatabaseSchema.PropertyStrict(typeof(T), value);
  150. }
  151. }
  152. public string Description => TriggerProperty is null
  153. ? $"{typeof(T).GetCaption()} changed"
  154. : $"{typeof(T).GetCaption()}.{TriggerProperty.Name} changed";
  155. public IEnumerable<string> ReferencedVariables => [];
  156. public bool Check(SaveEventDataModel<T> dataModel)
  157. {
  158. if(TriggerProperty is null)
  159. {
  160. return false;
  161. }
  162. if (!dataModel.Entity.HasOriginalValue(TriggerProperty.Name))
  163. {
  164. return false;
  165. }
  166. return true;
  167. }
  168. }
  169. [Caption("Filter")]
  170. public class FilterSaveEventTrigger<T> : IEventTrigger<SaveEvent<T>, SaveEventDataModel<T>>
  171. where T : Entity, new()
  172. {
  173. public Filter<T>? Filter { get; set; }
  174. public IEnumerable<string> ReferencedVariables => GetReferencedProperties(Filter).Select(x => $"{typeof(T).Name}.{x}");
  175. public string Description => Filter?.ToString() ?? "Blank Filter";
  176. public bool Check(SaveEventDataModel<T> dataModel)
  177. {
  178. return Filter.Match(dataModel.Entity, dataModel.Store.GetQueryProviderFactory());
  179. }
  180. private IEnumerable<string> GetReferencedProperties(Filter<T>? filter)
  181. {
  182. if (filter is null) yield break;
  183. if(filter.Operator != Operator.All && filter.Operator != Operator.None
  184. && !filter.Property.IsNullOrWhiteSpace())
  185. {
  186. yield return filter.Property;
  187. }
  188. foreach(var prop in filter.Ands.Concat(filter.Ors).SelectMany(GetReferencedProperties))
  189. {
  190. yield return prop;
  191. }
  192. }
  193. }
  194. [Caption("Custom Script")]
  195. public class ScriptSaveEventTrigger<T> : IEventTrigger<SaveEvent<T>, SaveEventDataModel<T>>
  196. where T : Entity, new()
  197. {
  198. public string Description => "Custom Script";
  199. private ScriptDocument? _scriptDocument;
  200. private ScriptDocument? ScriptDocument
  201. {
  202. get
  203. {
  204. if(_scriptDocument is null && Script is not null)
  205. {
  206. _scriptDocument = new(Script);
  207. _scriptDocument.Compile();
  208. }
  209. return _scriptDocument;
  210. }
  211. }
  212. private string? _script;
  213. public string? Script
  214. {
  215. get => _script;
  216. set
  217. {
  218. if(_script != value)
  219. {
  220. _script = value;
  221. _scriptDocument = null;
  222. }
  223. }
  224. }
  225. public IEnumerable<string> ReferencedVariables
  226. {
  227. get
  228. {
  229. var method = ScriptDocument?.GetMethod(methodName: "RequiredColumns");
  230. if(method is not null)
  231. {
  232. var cols = Columns.None<T>();
  233. method.Invoke(ScriptDocument!.GetObject(), [cols]);
  234. return cols.ColumnNames().Select(x => $"{typeof(T).Name}.{x}");
  235. }
  236. else
  237. {
  238. return [];
  239. }
  240. }
  241. }
  242. public string DefaultScript()
  243. {
  244. return @"using PRS.Shared.Events;
  245. public class Module
  246. {
  247. public void RequiredColumns(Columns<" + typeof(T).Name + @"> columns)
  248. {
  249. // Modify 'columns' as required.
  250. }
  251. public bool Check(SaveEventDataModel<" + typeof(T).Name + @"> model)
  252. {
  253. // Return true if model.Entity meets the requirements for this event trigger.
  254. return true;
  255. }
  256. }";
  257. }
  258. public bool Check(SaveEventDataModel<T> dataModel)
  259. {
  260. if (ScriptDocument is null) return false;
  261. return ScriptDocument.Execute(methodname: "Check", parameters: [dataModel]);
  262. }
  263. }
  264. #endregion
  265. #region Actions
  266. [Caption("Custom Script")]
  267. public class ScriptSaveEventAction<T> : IEventAction<SaveEvent<T>>
  268. where T : Entity, new()
  269. {
  270. private ScriptDocument? _scriptDocument;
  271. private ScriptDocument? ScriptDocument
  272. {
  273. get
  274. {
  275. if(_scriptDocument is null && Script is not null)
  276. {
  277. _scriptDocument = new(Script);
  278. _scriptDocument.SetValue("Result", null);
  279. _scriptDocument.Compile();
  280. }
  281. return _scriptDocument;
  282. }
  283. }
  284. private string? _script;
  285. public string? Script
  286. {
  287. get => _script;
  288. set
  289. {
  290. if(_script != value)
  291. {
  292. _script = value;
  293. _scriptDocument = null;
  294. }
  295. }
  296. }
  297. public IEnumerable<string> ReferencedVariables
  298. {
  299. get
  300. {
  301. var method = ScriptDocument?.GetMethod(methodName: "RequiredColumns");
  302. if(method is not null)
  303. {
  304. var cols = Columns.None<T>();
  305. method.Invoke(ScriptDocument!.GetObject(), [cols]);
  306. return cols.ColumnNames().Select(x => $"{typeof(T).Name}.{x}");
  307. }
  308. else
  309. {
  310. return [];
  311. }
  312. }
  313. }
  314. public string Description => "Custom Script";
  315. public string DefaultScript()
  316. {
  317. return @"using PRS.Shared.Events;
  318. public class Module
  319. {
  320. public object? Result { get; set; }
  321. public void RequiredColumns(Columns<" + typeof(T).Name + @"> columns)
  322. {
  323. // Modify 'columns' as required.
  324. }
  325. public bool Execute(SaveEventDataModel<" + typeof(T).Name + @"> model)
  326. {
  327. // Do anything you want with model.Entity, and then save return-value to 'Result', or leave it as 'null' if no return value is needed.
  328. return true;
  329. }
  330. }";
  331. }
  332. public object? Execute(IEventDataModel dataModel)
  333. {
  334. if (ScriptDocument is null) return null;
  335. var model = dataModel.RootModel<SaveEventDataModel<T>>();
  336. if(ScriptDocument.Execute(methodname: "Execute", parameters: [model]))
  337. {
  338. return ScriptDocument.GetValue("Result");
  339. }
  340. else
  341. {
  342. return null;
  343. }
  344. }
  345. }
  346. [Caption("Create Entity")]
  347. public class CreateEntitySaveEventAction<T> : IEventAction<SaveEvent<T>>
  348. where T : Entity, new()
  349. {
  350. [JsonProperty(Order = 1)]
  351. public Type? EntityType { get; set; }
  352. [JsonIgnore]
  353. public List<PropertyInitializer> Initializers { get; set; } = new List<PropertyInitializer>();
  354. public string Description => $"Create New {EntityType?.Name ?? "Entity"}";
  355. public IEnumerable<string> ReferencedVariables => Initializers.SelectMany(x => x.ReferencedVariables);
  356. public object? Execute(IEventDataModel dataModel)
  357. {
  358. if(EntityType is null)
  359. {
  360. return null;
  361. }
  362. var entity = (Activator.CreateInstance(EntityType) as Entity)!;
  363. foreach(var initializer in Initializers)
  364. {
  365. initializer.Execute(entity, dataModel);
  366. }
  367. DbFactory.FindStore(EntityType, Guid.Empty, "", default, "", Logger.Main).Save(entity, "");
  368. return entity;
  369. }
  370. #region Serialization Stuff
  371. [JsonProperty(Order = 2, PropertyName = "Initializers")]
  372. private PropertyInitializerSerializationItem[] _initializers
  373. {
  374. get => Initializers.ToArray(x => new PropertyInitializerSerializationItem { Property = x.Property.Name, Value = x.Value });
  375. set
  376. {
  377. Initializers.Clear();
  378. Initializers.AddRange(value.Select(x => new PropertyInitializer(EntityType!, x)));
  379. }
  380. }
  381. #endregion
  382. }
  383. internal class PropertyInitializerSerializationItem
  384. {
  385. public string Property { get; set; }
  386. public string Value { get; set; }
  387. }
  388. public class PropertyInitializer
  389. {
  390. public IProperty Property { get; set; }
  391. private CoreExpression? _valueExpression;
  392. private CoreExpression ValueExpression
  393. {
  394. get
  395. {
  396. _valueExpression ??= new CoreExpression(Value, Property.PropertyType);
  397. return _valueExpression;
  398. }
  399. }
  400. private string _value;
  401. public string Value
  402. {
  403. get => _value;
  404. [MemberNotNull(nameof(_value))]
  405. set
  406. {
  407. if(value != _value)
  408. {
  409. _value = value;
  410. _valueExpression = null;
  411. }
  412. }
  413. }
  414. public IEnumerable<string> ReferencedVariables => ValueExpression.ReferencedVariables;
  415. internal PropertyInitializer(Type entityType, PropertyInitializerSerializationItem item)
  416. {
  417. Value = item.Value;
  418. Property = DatabaseSchema.PropertyStrict(entityType, item.Property);
  419. }
  420. public PropertyInitializer(IProperty property, string value)
  421. {
  422. Property = property;
  423. Value = value;
  424. }
  425. public void Execute(Entity entity, IEventDataModel dataModel)
  426. {
  427. Property.Setter()(entity, ValueExpression.Evaluate(dataModel));
  428. }
  429. }
  430. #endregion