Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
StateMachineProcessor.cs
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Threading.Tasks;
5using System.Xml;
6using Waher.Content;
9using Waher.Events;
18using Waher.Script;
20using Waher.Security;
33
35{
39 public static class StateMachineProcessor
40 {
44 public const string StateMachineNamespace = "https://paiwise.tagroot.io/Schema/StateMachines.xsd";
45
49 public const string StateMachineDefinition = "StateMachine";
50
51 private static readonly Cache<CaseInsensitiveString, CacheRecord> stateMachines = new Cache<CaseInsensitiveString, CacheRecord>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromHours(1));
52 private static Dictionary<string, IStateMachineNode> stateMachineNodes = null;
53 private static LegalComponent legal;
54 private static EDalerComponent eDaler;
55
56 internal class CacheRecord
57 {
58 public StateMachine Machine;
60 public LegalComponent Legal;
62 public Profiler Profiler;
63 }
64
71 {
72 return Parse(Token.Definition, Token);
73 }
74
81 public static StateMachine Parse(string Xml, Token Token)
82 {
83 XmlDocument Doc = new XmlDocument();
84 Doc.LoadXml(Xml);
85
86 StateMachine Result = Parse(Doc, Token);
87
88 Result.XmlDefinition = Xml;
89
90 return Result;
91 }
92
99 public static StateMachine Parse(XmlDocument Xml, Token Token)
100 {
101 return Parse(Xml.DocumentElement, Token);
102 }
103
110 public static StateMachine Parse(XmlElement Xml, Token Token)
111 {
112 if (Xml is null || Xml.LocalName != "StateMachine" || Xml.NamespaceURI != StateMachineNamespace)
113 return null;
114
115 StateMachine Result = new StateMachine()
116 {
117 Root = (StateMachineRoot)Create(Xml),
118 XmlDefinition = Xml.OuterXml,
119 DefinitionContractId = Token.CreationContract,
120 TrustProvider = Token.TrustProvider,
121 TrustProviderJid = Token.TrustProviderJid,
122 CreatorTokenId = Token.TokenId
123 };
124
125 Result.CheckReferences(Token);
126
127 return Result;
128 }
129
135 internal static IStateMachineNode Create(XmlElement Xml)
136 {
138
139 if (stateMachineNodes is null)
140 {
141 Dictionary<string, IStateMachineNode> ByFqn = new Dictionary<string, IStateMachineNode>();
142
143 foreach (Type T in Types.GetTypesImplementingInterface(typeof(IStateMachineNode)))
144 {
145 if (T.IsAbstract || T.IsInterface || T.IsGenericTypeDefinition)
146 continue;
147
148 try
149 {
151 ByFqn[Node.Namespace + "#" + Node.LocalName] = Node;
152 }
153 catch (Exception ex)
154 {
155 Log.Exception(ex);
156 }
157 }
158
159 stateMachineNodes = ByFqn;
160 }
161
162 string Fqn = Xml.NamespaceURI + "#" + Xml.LocalName;
163 if (!stateMachineNodes.TryGetValue(Fqn, out Node))
164 throw new Exception("A State-Machine node named " + Fqn + " not known.");
165
166 Node = Node.Create();
167 Node.Parse(Xml);
168
169 return Node;
170 }
171
177 internal static async Task EventGenerated(Token Token, TokenEvent Event)
178 {
180 return;
181
182 CacheRecord CacheRecord = await GetStateMachine(Token);
183 if (CacheRecord is null)
184 return;
185
186 await EventGenerated(Token, Event, CacheRecord);
187 }
188
189 private static async Task EventGenerated(Token Token, TokenEvent Event, CacheRecord CacheRecord)
190 {
191 StateMachine Machine = CacheRecord.Machine;
192
193 try
194 {
196 await TokenTransferred(Token, Transferred, Machine);
197 else if (Event is NoteText NoteText)
198 await NoteAdded(Token, NoteText, Machine);
199 else if (Event is NoteXml NoteXml)
200 await NoteAdded(Token, NoteXml, Machine);
202 await NoteAdded(Token, ExternalNoteText, Machine);
204 await NoteAdded(Token, ExternalNoteXml, Machine);
205 else if (Event is Destroyed Destroyed)
206 await TokenDestroyed(Token, Destroyed, Machine);
207 }
208 catch (Exception ex)
209 {
210 if (!(CacheRecord.Profiler is null))
211 {
212 int NoteNr = CacheRecord.Profiler.AddNote(ex);
213 CacheRecord.Profiler?.Exception(ex, "Note" + NoteNr);
214 }
215
217 stateMachines.Remove(Machine.StateMachineId); // Might be in an incorrect state. By removing it from the cache, a new version of the state machine is loaded next time.
218 }
219 }
220
226 internal static async Task EventGenerated(string StateMachineId, TokenEvent Event)
227 {
228 CacheRecord CacheRecord = await GetStateMachine(StateMachineId);
229 if (CacheRecord is null)
230 return;
231
232 Token Token = await NeuroFeaturesProcessor.GetToken(StateMachineId, true);
233
234 await EventGenerated(Token, Event, CacheRecord);
235 }
236
242 internal static Task<CacheRecord> GetStateMachine(Token Token)
243 {
244 return GetStateMachine(Token.MachineId);
245 }
246
253 internal static async Task<CurrentState> GetOrCreateCurrentState(StateMachine Machine)
254 {
255 using (Semaphore Semaphore = await Semaphores.BeginWrite("machine:" + Machine.StateMachineId.ToString()))
256 {
257 CurrentState CurrentState = await Database.FindFirstIgnoreRest<CurrentState>(new FilterFieldEqualTo("StateMachineId", Machine.StateMachineId));
258 if (CurrentState is null)
259 {
261 {
262 StateMachineId = Machine.StateMachineId,
263 VariableValues = new CurrentStateVariable[0],
264 State = string.Empty,
265 Expires = Machine.Expires,
266 ArchiveRequired = Machine.ArchiveRequired,
267 ArchiveOptional = Machine.ArchiveOptional
268 };
269
271 }
272
273 return CurrentState;
274 }
275 }
276
282 internal static async Task<CacheRecord> GetStateMachine(string MachineId)
283 {
284 if (stateMachines.TryGetValue(MachineId, out CacheRecord Result))
285 return Result;
286
287 StateMachine Machine = await Database.FindFirstIgnoreRest<StateMachine>(new FilterFieldEqualTo("StateMachineId", MachineId));
288 if (Machine is null)
289 return null;
290
291 Machine.IndexElements();
292
293 CurrentState CurrentState = await GetOrCreateCurrentState(Machine);
294
295 Result = new CacheRecord()
296 {
297 Machine = Machine,
299 EDaler = eDaler,
300 Legal = legal,
301 Profiler = new Profiler(MachineId, ProfilerThreadType.StateMachine)
302 };
303
304 stateMachines[MachineId] = Result;
305 Result.Profiler.NewState(CurrentState.State);
306 Result.Profiler.Start();
307 Result.Profiler.Event("Reloaded");
308
309 return Result;
310 }
311
317 internal static async Task ModuleStarted(LegalComponent Legal, EDalerComponent EDaler)
318 {
319 legal = Legal;
320 eDaler = EDaler;
321
322 Ledger.EntryAdded += Ledger_EntryAdded;
323 Ledger.EntryUpdated += Ledger_EntryUpdated;
324 Ledger.EntryDeleted += Ledger_EntryDeleted;
325
326 LinkedList<TimepointEventHandler> ToDelete = null;
327
328 try
329 {
330 foreach (TimepointEventHandler Handler in await Database.Find<TimepointEventHandler>(
331 new FilterFieldEqualTo("EventType", nameof(TimepointEventHandler))))
332 {
333 if (await ReregisterOnStart(Handler))
334 {
335 if (ToDelete is null)
336 ToDelete = new LinkedList<TimepointEventHandler>();
337
338 ToDelete.AddLast(Handler);
339 }
340 }
341 }
342 finally
343 {
344 if (!(ToDelete is null))
345 await Database.Delete(ToDelete);
346 }
347 }
348
354 private static async Task<bool> ReregisterOnStart(TimepointEventHandler Handler)
355 {
356 try
357 {
358 CacheRecord CacheRecord = await GetStateMachine(Handler.StateMachineId);
359 if (CacheRecord is null)
360 return true;
361
362 StateMachine Machine = CacheRecord.Machine;
364 if (Handler.State != CurrentState.State)
365 return true;
366
367 if (!Machine.TryGetState(Handler.State, out State State))
368 return true;
369
370 if (State.OnEvent is null || State.OnEvent.Length <= Handler.EventIndex)
371 return true;
372
375 return true;
376
377 await TimedEventNode.ReregisterOnStart(Handler);
378 return false;
379 }
380 catch (Exception ex)
381 {
382 Log.Exception(ex);
383 return true;
384 }
385 }
386
390 internal static Task ModuleStopped()
391 {
392 legal = null;
393 eDaler = null;
394
395 Ledger.EntryAdded -= Ledger_EntryAdded;
396 Ledger.EntryUpdated -= Ledger_EntryUpdated;
397 Ledger.EntryDeleted -= Ledger_EntryDeleted;
398
399 TimedEventNode.ClearPendingEvents();
400
401 return Task.CompletedTask;
402 }
403
411 internal static async Task Start(StateMachine Machine, Token Token, LegalComponent Legal, EDalerComponent EDaler)
412 {
413 try
414 {
415 Profiler Profiler = new Profiler(Machine.StateMachineId, ProfilerThreadType.StateMachine);
416 Profiler.Start();
417
419
420 stateMachines[Machine.StateMachineId] = new CacheRecord()
421 {
423 Machine = Machine,
424 EDaler = EDaler,
425 Legal = Legal,
427 };
428
429 await Machine.Start(Arguments);
430 }
431 catch (Exception ex)
432 {
433 Log.Exception(ex, Machine.ObjectId,
434 new KeyValuePair<string, object>("StateMachineId", Machine.StateMachineId.Value));
435 }
436 }
437
438 internal static async Task<CacheRecord> EventRaised(EventHandlers.EventHandler Handler, Token Token, params KeyValuePair<string, object>[] ToSet)
439 {
440 CacheRecord CacheRecord = await GetStateMachine(Handler.StateMachineId);
441 if (CacheRecord is null)
442 return null;
443
444 try
445 {
446 StateMachine Machine = CacheRecord.Machine;
448 if (Handler.State != CurrentState.State)
449 return null;
450
451 if (!Machine.TryGetState(Handler.State, out State State))
452 return null;
453
454 if (State.OnEvent is null || State.OnEvent.Length <= Handler.EventIndex)
455 return null;
456
458
459 if (!(ToSet is null))
460 {
461 foreach (KeyValuePair<string, object> P in ToSet)
462 Variables[P.Key] = P.Value;
463 }
464
466 Token = await NeuroFeaturesProcessor.GetToken(Machine.CreatorTokenId, false);
467
468 EvaluationArguments Arguments = new EvaluationArguments(Variables, Machine,
469 Token, CurrentState, CacheRecord.Legal ?? legal,
470 CacheRecord.EDaler ?? eDaler, CacheRecord.Profiler);
471
472 try
473 {
475 string StateId;
476
477 try
478 {
479 await Event.ExecuteLog(Arguments);
480 StateId = await Event.GetNewState(Arguments);
481 }
482 catch (Exception ex)
483 {
484 StateId = await Event.GetFailureState(Arguments);
485
486 if (string.IsNullOrEmpty(StateId))
487 Log.Error(ex, Arguments.Machine.ObjectId);
488 }
489
490 if (string.IsNullOrEmpty(StateId))
491 await StateMachine.CheckConditionalEvents(Arguments);
492 else
493 await StateMachine.GoToState(StateId, Arguments);
494 }
495 finally
496 {
497 await StateMachine.EvaluationComplete(Arguments);
498 }
499 }
500 catch (Exception ex)
501 {
502 if (!(CacheRecord.Profiler is null))
503 {
504 int NoteNr = CacheRecord.Profiler.AddNote(ex);
505 CacheRecord.Profiler?.Exception(ex, "Note" + NoteNr);
506 }
507
508 Log.Exception(ex, Handler.StateMachineId);
509 }
510
511 return CacheRecord;
512 }
513
520 internal static async Task NoteAdded(Token Token, NoteText Event, StateMachine Machine)
521 {
523 nameof(TokenNoteEventHandler) + "|" + nameof(OnTextNote) + "|" +
524 Machine.StateMachineId + "||");
525
526 if ((Handlers?.Length ?? 0) == 0)
527 return;
528
529 foreach (CachedEventHandler Handler in Handlers)
530 {
532 await EventRaised(Handler, Token, GetVariablesToSet(TokenNoteEventHandler, Event.Note, Event.Personal, null));
533 }
534 }
535
542 internal static async Task NoteAdded(Token Token, NoteXml Event, StateMachine Machine)
543 {
545 nameof(TokenNoteEventHandler) + "|" + nameof(OnXmlNote) + "|" +
546 Machine.StateMachineId + "|" + Event.LocalName + "|" + Event.Namespace);
547
548 if ((Handlers?.Length ?? 0) == 0)
549 return;
550
551 XmlDocument Doc = null;
552
553 foreach (CachedEventHandler Handler in Handlers)
554 {
556 {
557 if (Doc is null)
558 {
559 Doc = new XmlDocument();
560 Doc.LoadXml(Event.Note);
561 }
562
563 await EventRaised(Handler, Token, GetVariablesToSet(TokenNoteEventHandler, Doc, Event.Personal, null));
564 }
565 }
566 }
567
574 internal static async Task NoteAdded(Token Token, ExternalNoteText Event, StateMachine Machine)
575 {
577 nameof(TokenNoteEventHandler) + "|" + nameof(OnExternalTextNote) + "|" +
578 Machine.StateMachineId + "||");
579
580 if ((Handlers?.Length ?? 0) == 0)
581 return;
582
583
584 foreach (CachedEventHandler Handler in Handlers)
585 {
587 {
588 if (!await IsAuthorized(Event.Source, TokenNoteEventHandler.Privilege))
589 continue;
590
591 await EventRaised(Handler, Token, GetVariablesToSet(TokenNoteEventHandler, Event.Note, Event.Personal, Event.Source));
592 }
593 }
594 }
595
602 internal static async Task NoteAdded(Token Token, ExternalNoteXml Event, StateMachine Machine)
603 {
605 nameof(TokenNoteEventHandler) + "|" + nameof(OnExternalXmlNote) + "|" +
606 Machine.StateMachineId + "|" + Event.LocalName + "|" + Event.Namespace);
607
608 if ((Handlers?.Length ?? 0) == 0)
609 return;
610
611 XmlDocument Doc = null;
612
613 foreach (CachedEventHandler Handler in Handlers)
614 {
616 {
617 if (!await IsAuthorized(Event.Source, TokenNoteEventHandler.Privilege))
618 continue;
619
620 if (Doc is null)
621 {
622 Doc = new XmlDocument();
623 Doc.LoadXml(Event.Note);
624 }
625
626 await EventRaised(Handler, Token, GetVariablesToSet(TokenNoteEventHandler, Doc, Event.Personal, Event.Source));
627 }
628 }
629 }
630
631 private static async Task<bool> IsAuthorized(string Source, string Privilege)
632 {
633 bool CheckPrivilege = !string.IsNullOrEmpty(Privilege);
634
635 if (!CheckPrivilege && Source.IndexOf('@') > 0)
636 return true;
637
638 IUser User = await Users.GetUser(Source, false);
639 if (User is null)
640 return false;
641
643 }
644
645 private static KeyValuePair<string, object>[] GetVariablesToSet(TokenNoteEventHandler Handler, object Note, bool Personal, string Source)
646 {
647 List<KeyValuePair<string, object>> Result = new List<KeyValuePair<string, object>>();
648
649 if (!string.IsNullOrEmpty(Handler.SourceVariable))
650 Result.Add(new KeyValuePair<string, object>(Handler.SourceVariable, Source));
651
652 if (!string.IsNullOrEmpty(Handler.PersonalVariable))
653 Result.Add(new KeyValuePair<string, object>(Handler.PersonalVariable, Personal));
654
655 if (!string.IsNullOrEmpty(Handler.NoteVariable))
656 Result.Add(new KeyValuePair<string, object>(Handler.NoteVariable, Note));
657
658 return Result.ToArray();
659 }
660
667 internal static async Task TokenTransferred(Token Token, Transferred Event, StateMachine Machine)
668 {
670 nameof(TokenTransferredEventHandler) + "|" + nameof(OnTransferred) + "|" +
671 Machine.StateMachineId);
672
673 if ((Handlers?.Length ?? 0) == 0)
674 return;
675
676 foreach (CachedEventHandler Handler in Handlers)
677 {
679 await EventRaised(Handler, Token, GetVariablesToSet(TokenTransferredEventHandler, Event));
680 }
681 }
682
683 private static KeyValuePair<string, object>[] GetVariablesToSet(
685 {
686 List<KeyValuePair<string, object>> Result = new List<KeyValuePair<string, object>>();
687
688 if (!string.IsNullOrEmpty(Handler.SellerVariable))
689 Result.Add(new KeyValuePair<string, object>(Handler.SellerVariable, Event.Seller));
690
691 if (!string.IsNullOrEmpty(Handler.BuyerVariable))
692 Result.Add(new KeyValuePair<string, object>(Handler.BuyerVariable, Event.Owner));
693
694 if (!string.IsNullOrEmpty(Handler.ContractVariable))
695 Result.Add(new KeyValuePair<string, object>(Handler.ContractVariable, Event.OwnershipContract));
696
697 if (!string.IsNullOrEmpty(Handler.ValueVariable))
698 Result.Add(new KeyValuePair<string, object>(Handler.ValueVariable, Event.Value));
699
700 if (!string.IsNullOrEmpty(Handler.AmountVariable))
701 Result.Add(new KeyValuePair<string, object>(Handler.AmountVariable, Event.Value - Event.Commission));
702
703 if (!string.IsNullOrEmpty(Handler.CurrencyVariable))
704 Result.Add(new KeyValuePair<string, object>(Handler.CurrencyVariable, Event.Currency));
705
706 return Result.ToArray();
707 }
708
714 internal static async Task EDalerSent(XmppAddress Owner, EDalerUri Uri)
715 {
717 nameof(OwnerEventHandler) + "|" + nameof(OnPaymentSent) + "|" + Owner.BareJid.LowerCase);
718
719 if ((Handlers?.Length ?? 0) == 0)
720 return;
721
722 foreach (CachedEventHandler Handler in Handlers)
723 {
724 if (Handler is PaymentEventHandler PaymentHandler)
725 await EventRaised(Handler, null, GetVariablesToSet(PaymentHandler, Uri, Uri.To.BareJid));
726 }
727 }
728
735 internal static async Task EDalerReceived(XmppAddress Owner, EDalerUri Uri, string Sender)
736 {
738 nameof(OwnerEventHandler) + "|" + nameof(OnPaymentReceived) + "|" + Owner.BareJid.LowerCase);
739
740 if ((Handlers?.Length ?? 0) == 0)
741 return;
742
743 XmppAddress SenderAddress = new XmppAddress(Sender);
744 Sender = SenderAddress.BareJid;
745
746 foreach (CachedEventHandler Handler in Handlers)
747 {
748 if (Handler is PaymentEventHandler PaymentHandler)
749 await EventRaised(Handler, null, GetVariablesToSet(PaymentHandler, Uri, Sender));
750 }
751 }
752
753 private static KeyValuePair<string, object>[] GetVariablesToSet(PaymentEventHandler Handler, EDalerUri Uri, string RemoteAddress)
754 {
755 List<KeyValuePair<string, object>> Result = new List<KeyValuePair<string, object>>();
756
757 if (!string.IsNullOrEmpty(Handler.RemoteVariable))
758 Result.Add(new KeyValuePair<string, object>(Handler.RemoteVariable, RemoteAddress));
759
760 if (!string.IsNullOrEmpty(Handler.AmountVariable))
761 Result.Add(new KeyValuePair<string, object>(Handler.AmountVariable, (double)Uri.Amount));
762
763 if (!string.IsNullOrEmpty(Handler.AmountExtraVariable))
764 Result.Add(new KeyValuePair<string, object>(Handler.AmountExtraVariable, (double)(Uri.AmountExtra ?? 0M)));
765
766 if (!string.IsNullOrEmpty(Handler.AmountTotalVariable))
767 Result.Add(new KeyValuePair<string, object>(Handler.AmountTotalVariable, (double)Uri.TotalAmount));
768
769 if (!string.IsNullOrEmpty(Handler.CurrencyVariable))
770 Result.Add(new KeyValuePair<string, object>(Handler.CurrencyVariable, Uri.Currency));
771
772 if (!string.IsNullOrEmpty(Handler.ReferenceVariable))
773 {
774 if (Uri.EncryptionPublicKey is null && !(Uri.EncryptedMessage is null))
775 Result.Add(new KeyValuePair<string, object>(Handler.ReferenceVariable, Encoding.UTF8.GetString(Uri.EncryptedMessage)));
776 else
777 Result.Add(new KeyValuePair<string, object>(Handler.ReferenceVariable, null));
778 }
779
780 if (!string.IsNullOrEmpty(Handler.ConditionVariable))
781 Result.Add(new KeyValuePair<string, object>(Handler.ConditionVariable, Uri.ContractCondition?.Value));
782
783 return Result.ToArray();
784 }
785
792 internal static Task TokenDestroyed(Token Token, Destroyed Event, StateMachine Machine)
793 {
794 return Task.CompletedTask; // TODO
795 }
796
797 private static void Ledger_EntryAdded(object Sender, ObjectEventArgs e)
798 {
799 // TODO
800 }
801
802 private static void Ledger_EntryUpdated(object Sender, ObjectEventArgs e)
803 {
804 // TODO
805 }
806
807 private static void Ledger_EntryDeleted(object Sender, ObjectEventArgs e)
808 {
809 // TODO
810 }
811
812 internal static async Task<(int, int, int, int)> DeleteExpiredMachines()
813 {
814 int NrMachines = 0;
815 int NrEventHandlers = 0;
816 int NrCurrentStates = 0;
817 int NrSamples = 0;
818
819 IEnumerable<StateMachine> Deleted = await Database.FindDelete<StateMachine>(
820 new FilterFieldLesserThan("Expires", DateTime.Today.AddDays(-1)));
821
822 foreach (StateMachine Machine in Deleted)
823 {
824 NrMachines++;
825
826 if (stateMachines.TryGetValue(Machine.StateMachineId, out CacheRecord Record))
827 {
828 stateMachines.Remove(Machine.StateMachineId);
829
830 if (!string.IsNullOrEmpty(Record.CurrentState?.State) &&
831 Machine.TryGetState(Record.CurrentState.State, out State State) &&
832 !(State.OnEvent is null))
833 {
834 try
835 {
836 Variables Variables = Record.CurrentState.GetVariables(Machine);
838 Machine, null, Record.CurrentState, Record.Legal, Record.EDaler,
839 Record.Profiler);
840
842 }
843 catch (Exception ex)
844 {
845 Log.Exception(ex);
846 }
847 }
848 }
849
850 IEnumerable<EventHandlers.EventHandler> EventHandlers = await Database.FindDelete<EventHandlers.EventHandler>(
851 new FilterFieldEqualTo("StateMachineId", Machine.StateMachineId));
852
853 foreach (EventHandlers.EventHandler EventHandler in EventHandlers)
854 {
855 NrEventHandlers++;
856
859 }
860
861 IEnumerable<CurrentState> CurrentStates = await Database.FindDelete<CurrentState>(
862 new FilterFieldEqualTo("StateMachineId", Machine.StateMachineId));
863
864 foreach (CurrentState CurrentState in CurrentStates)
865 NrCurrentStates++;
866
867 IEnumerable<StateMachineSample> Samples = await Database.FindDelete<StateMachineSample>(
868 new FilterFieldEqualTo("StateMachineId", Machine.StateMachineId.Value));
869
870 foreach (StateMachineSample Sample in Samples)
871 NrSamples++;
872 }
873
874 return (NrMachines, NrEventHandlers, NrCurrentStates, NrSamples);
875 }
876
877 #region XMPP Interface
878
879 internal static void RegisterHandlers(EDalerComponent EDaler)
880 {
881 eDaler = EDaler;
882
883 EDaler.RegisterIqGetHandler("currentState", StateMachineNamespace, GetCurrentStateHandler, true);
884 EDaler.RegisterIqGetHandler("profilingReport", StateMachineNamespace, GetProfilingReport, false);
885 EDaler.RegisterIqGetHandler("presentReport", StateMachineNamespace, GetPresentReport, false);
886 EDaler.RegisterIqGetHandler("historyReport", StateMachineNamespace, GetHistoryReport, false);
887 EDaler.RegisterIqGetHandler("stateDiagram", StateMachineNamespace, GetStateDiagram, false);
888 }
889
890 internal static void UnregisterHandlers(EDalerComponent EDaler)
891 {
892 eDaler = null;
893
894 EDaler.UnregisterIqGetHandler("currentState", StateMachineNamespace, GetCurrentStateHandler, true);
895 EDaler.UnregisterIqGetHandler("profilingReport", StateMachineNamespace, GetProfilingReport, false);
896 EDaler.UnregisterIqGetHandler("presentReport", StateMachineNamespace, GetPresentReport, false);
897 EDaler.UnregisterIqGetHandler("historyReport", StateMachineNamespace, GetHistoryReport, false);
898 EDaler.UnregisterIqGetHandler("stateDiagram", StateMachineNamespace, GetStateDiagram, false);
899 }
900
901 #region GetCurrentStateHandler
902
903 private static async Task GetCurrentStateHandler(object Sender, IqEventArgs e)
904 {
905 string TokenId = XML.Attribute(e.Query, "tokenId");
906 if (string.IsNullOrEmpty(TokenId))
907 {
908 await e.IqErrorBadRequest(e.To, "Missing Token ID.", "en");
909 return;
910 }
911
912 CurrentState CurrentState = await GetCurrentState(TokenId, e);
913 if (CurrentState is null)
914 return;
915
916 // TODO: Avoid exporting during active change, to protect integrity of state object.
917
918 StringBuilder Xml = new StringBuilder();
919 Export(CurrentState, Xml);
920
921 await e.IqResult(Xml.ToString(), e.To);
922 }
923
924 internal static void Export(CurrentState CurrentState, StringBuilder Xml)
925 {
926 Xml.Append("<currentState xmlns='");
927 Xml.Append(StateMachineNamespace);
928 Xml.Append("' state='");
929 Xml.Append(XML.Encode(CurrentState.State));
930 Xml.Append("' ended='");
932 Xml.Append("' running='");
934 Xml.Append("' expires='");
935 Xml.Append(XML.Encode(CurrentState.Expires));
936 Xml.Append("'>");
937
938 if (!(CurrentState.VariableValues is null))
939 {
941 AppendVariable(Xml, Variable.Name, Variable.Value);
942 }
943
944 Xml.Append("</currentState>");
945 }
946
947 internal static void AppendVariable(StringBuilder Xml, string Name, object Value)
948 {
949 Xml.Append("<variable name='");
950 Xml.Append(XML.Encode(Name));
951 Xml.Append("'>");
952
953 if (Value is null)
954 Xml.Append("<null/>");
955 else if (Value is double dbl)
956 {
957 Xml.Append("<dbl>");
958 Xml.Append(CommonTypes.Encode(dbl));
959 Xml.Append("</dbl>");
960 }
961 else if (Value is float fl)
962 {
963 Xml.Append("<fl>");
964 Xml.Append(CommonTypes.Encode(fl));
965 Xml.Append("</fl>");
966 }
967 else if (Value is decimal dec)
968 {
969 Xml.Append("<dec>");
970 Xml.Append(CommonTypes.Encode(dec));
971 Xml.Append("</dec>");
972 }
973 else if (Value is int i32)
974 {
975 Xml.Append("<i32>");
976 Xml.Append(i32.ToString());
977 Xml.Append("</i32>");
978 }
979 else if (Value is long i64)
980 {
981 Xml.Append("<i64>");
982 Xml.Append(i64.ToString());
983 Xml.Append("</i64>");
984 }
985 else if (Value is short i16)
986 {
987 Xml.Append("<i16>");
988 Xml.Append(i16.ToString());
989 Xml.Append("</i16>");
990 }
991 else if (Value is sbyte i8)
992 {
993 Xml.Append("<i8>");
994 Xml.Append(i8.ToString());
995 Xml.Append("</i8>");
996 }
997 else if (Value is uint ui32)
998 {
999 Xml.Append("<ui32>");
1000 Xml.Append(ui32.ToString());
1001 Xml.Append("</ui32>");
1002 }
1003 else if (Value is ulong ui64)
1004 {
1005 Xml.Append("<ui64>");
1006 Xml.Append(ui64.ToString());
1007 Xml.Append("</ui64>");
1008 }
1009 else if (Value is ushort ui16)
1010 {
1011 Xml.Append("<ui16>");
1012 Xml.Append(ui16.ToString());
1013 Xml.Append("</ui16>");
1014 }
1015 else if (Value is byte ui8)
1016 {
1017 Xml.Append("<ui8>");
1018 Xml.Append(ui8.ToString());
1019 Xml.Append("</ui8>");
1020 }
1021 else if (Value is bool b)
1022 {
1023 Xml.Append("<b>");
1024 Xml.Append(CommonTypes.Encode(b));
1025 Xml.Append("</b>");
1026 }
1027 else if (Value is DateTime TP)
1028 {
1029 Xml.Append("<dt>");
1030 Xml.Append(XML.Encode(TP));
1031 Xml.Append("</dt>");
1032 }
1033 else if (Value is DateTimeOffset TPO)
1034 {
1035 Xml.Append("<dto>");
1036 Xml.Append(XML.Encode(TPO));
1037 Xml.Append("</dto>");
1038 }
1039 else if (Value is TimeSpan TS)
1040 {
1041 Xml.Append("<ts>");
1042 Xml.Append(XML.Encode(TS.ToString()));
1043 Xml.Append("</ts>");
1044 }
1045 else if (Value is Duration D)
1046 {
1047 Xml.Append("<d>");
1048 Xml.Append(XML.Encode(D.ToString()));
1049 Xml.Append("</d>");
1050 }
1051 else if (Value is string s)
1052 {
1053 Xml.Append("<s>");
1054 Xml.Append(XML.Encode(s));
1055 Xml.Append("</s>");
1056 }
1057 else
1058 {
1059 Xml.Append("<exp>");
1060 Xml.Append(XML.Encode(Expression.ToString(Value)));
1061 Xml.Append("</exp>");
1062 }
1063
1064 Xml.Append("</variable>");
1065 }
1066
1067 private static async Task<CacheRecord> GetCacheRecord(string TokenId, IqEventArgs e)
1068 {
1069 XmppAddress Addr = new XmppAddress(TokenId);
1070 if (!Addr.IsBareJID || !Guid.TryParse(Addr.Account, out _))
1071 {
1072 e?.IqErrorBadRequest(e.To, "Invalid Token ID.", "en");
1073 return null;
1074 }
1075
1076 if (!eDaler.IsComponentDomain(Addr.Domain, true))
1077 {
1078 e?.IqErrorBadRequest(e.To, "Token not hosted by this neuron.", "en");
1079 return null;
1080 }
1081
1082 Token Token = await NeuroFeaturesProcessor.GetToken(TokenId, true);
1083 if (Token is null)
1084 {
1085 e?.IqErrorItemNotFound(e.To, "Token not found.", "en");
1086 return null;
1087 }
1088
1089 if (!(e is null) && !await NeuroFeaturesProcessor.IsAuthorizedAccess(Token, e))
1090 return null;
1091
1092 CacheRecord Record = await GetStateMachine(Token.MachineId);
1093 if (Record is null)
1094 {
1095 e?.IqErrorItemNotFound(e.To, "State-Machine not found.", "en");
1096 return null;
1097 }
1098
1099 return Record;
1100 }
1101
1102 private static async Task<CurrentState> GetCurrentState(string TokenId, IqEventArgs e)
1103 {
1104 CacheRecord Record = await GetCacheRecord(TokenId, e);
1105 if (Record is null)
1106 return null;
1107
1108 return Record.CurrentState;
1109 }
1110
1111 #endregion
1112
1113 #region GetProfilingReport
1114
1115 internal static async Task<string> GetProfilingReportAsync(string TokenId, ReportFormat Format)
1116 {
1117 CacheRecord Record = await GetCacheRecord(TokenId, null);
1118 if (Record?.Profiler is null)
1119 return string.Empty;
1120
1121 return await GetProfilingReport(Record.Profiler, Format);
1122 }
1123
1124 private static async Task GetProfilingReport(object Sender, IqEventArgs e)
1125 {
1126 string TokenId = XML.Attribute(e.Query, "tokenId");
1127 if (string.IsNullOrEmpty(TokenId))
1128 {
1129 await e.IqErrorBadRequest(e.To, "Missing Token ID.", "en");
1130 return;
1131 }
1132
1133 CacheRecord Record = await GetCacheRecord(TokenId, e);
1134 if (Record is null)
1135 return;
1136
1137 // TODO: Avoid exporting during active change, to protect integrity of state object.
1138
1139 if (Record?.Profiler is null)
1140 {
1141 await e.IqErrorServiceUnavailable(e.To, "Profiler not activated on the state-machine.", "en");
1142 return;
1143 }
1144
1145 ReportFormat ReportFormat = XML.Attribute(e.Query, "format", ReportFormat.Markdown);
1146 StringBuilder Xml = new StringBuilder();
1147
1148 Xml.Append("<report xmlns='");
1149 Xml.Append(StateMachineNamespace);
1150 Xml.Append("'>");
1151 Xml.Append(XML.Encode(await GetProfilingReport(Record.Profiler, ReportFormat)));
1152 Xml.Append("</report>");
1153
1154 await e.IqResult(Xml.ToString(), e.To);
1155 }
1156
1157 private static Task<string> GetProfilingReport(Profiler Profiler, ReportFormat Format)
1158 {
1159 StringBuilder Markdown = new StringBuilder();
1160
1161 Markdown.AppendLine("Timing Diagram");
1162 Markdown.AppendLine("==================");
1163 Markdown.AppendLine();
1164 Markdown.AppendLine("```uml");
1165 Profiler.ExportPlantUml(Markdown, TimeUnit.DynamicPerProfiling);
1166 Markdown.AppendLine("```");
1167
1168 int i, c = Profiler.NoteCount;
1169
1170 for (i = 1; i <= c; i++)
1171 {
1172 if (Profiler.TryGetNote(i, out object Note))
1173 {
1174 Markdown.AppendLine();
1175 Markdown.Append("Note ");
1176 Markdown.AppendLine(i.ToString());
1177 Markdown.AppendLine("-----------------");
1178 Markdown.AppendLine();
1179
1180 if (Note is string s)
1181 {
1182 if (s.StartsWith("@startjson") && s.EndsWith("@endjson"))
1183 {
1184 Markdown.AppendLine("```uml");
1185 Markdown.AppendLine(s);
1186 Markdown.AppendLine("```");
1187 }
1188 else
1189 Markdown.AppendLine(MarkdownDocument.Encode(s));
1190 }
1191 else if (Note is ScriptRuntimeException ScriptError)
1192 {
1193 Markdown.AppendLine(MarkdownDocument.Encode(ScriptError.Message));
1194 Markdown.AppendLine();
1195 Markdown.AppendLine("```");
1196 Markdown.AppendLine(ScriptError.Node?.SubExpression ?? ScriptError.StackTrace);
1197 Markdown.AppendLine("```");
1198 }
1199 else if (Note is Exception ex)
1200 {
1201 Markdown.AppendLine(MarkdownDocument.Encode(ex.Message));
1202 Markdown.AppendLine();
1203 Markdown.AppendLine("```");
1204 Markdown.AppendLine(ex.StackTrace);
1205 Markdown.AppendLine("```");
1206 }
1207 else
1208 {
1209 Markdown.AppendLine("```uml");
1210 Markdown.AppendLine("@startjson");
1211 Markdown.AppendLine(JSON.Encode(Note, true));
1212 Markdown.AppendLine("@endjson");
1213 Markdown.AppendLine("```");
1214 }
1215 }
1216 }
1217
1218 return NeuroFeaturesProcessor.FormatReport(Markdown.ToString(), Format, HttpServer.CreateVariables());
1219 }
1220
1221 #endregion
1222
1223 #region GetPresentReport
1224
1225 internal static async Task<string> GetPresentReportAsync(Token Token, ReportFormat Format)
1226 {
1227 CacheRecord Record = await GetStateMachine(Token);
1228 if (Record is null)
1229 return string.Empty;
1230
1231 return await GetPresentReport(Record, Format);
1232 }
1233
1234 private static async Task GetPresentReport(object Sender, IqEventArgs e)
1235 {
1236 string TokenId = XML.Attribute(e.Query, "tokenId");
1237 if (string.IsNullOrEmpty(TokenId))
1238 {
1239 await e.IqErrorBadRequest(e.To, "Missing Token ID.", "en");
1240 return;
1241 }
1242
1243 CacheRecord Record = await GetCacheRecord(TokenId, e);
1244 if (Record is null)
1245 return;
1246
1247 // TODO: Avoid exporting during active change, to protect integrity of state object.
1248
1249 ReportFormat ReportFormat = XML.Attribute(e.Query, "format", ReportFormat.Markdown);
1250 StringBuilder Xml = new StringBuilder();
1251
1252 Xml.Append("<report xmlns='");
1253 Xml.Append(StateMachineNamespace);
1254 Xml.Append("'>");
1255 Xml.Append(XML.Encode(await GetPresentReport(Record, ReportFormat)));
1256 Xml.Append("</report>");
1257
1258 await e.IqResult(Xml.ToString(), e.To);
1259 }
1260
1261 private static Task<string> GetPresentReport(CacheRecord Record, ReportFormat Format)
1262 {
1263 string Markdown = null;
1264
1265 foreach (IStateMachineNode Node in Record.Machine.Root.ChildNodes)
1266 {
1267 if (Node is ReportPresent Report)
1268 {
1269 Markdown = Report.Markdown;
1270 break;
1271 }
1272 }
1273
1274 if (string.IsNullOrEmpty(Markdown))
1275 return Task.FromResult(string.Empty);
1276
1277 Variables Variables = Record.CurrentState.GetVariables(Record.Machine);
1278
1279 return NeuroFeaturesProcessor.FormatReport(Markdown, Format, Variables);
1280 }
1281
1282 #endregion
1283
1284 #region GetHistoryReport
1285
1286 internal static async Task<string> GetHistoryReportAsync(Token Token, ReportFormat Format)
1287 {
1288 CacheRecord Record = await GetStateMachine(Token);
1289 if (Record is null)
1290 return string.Empty;
1291
1292 return await GetHistoryReport(Record, Format);
1293 }
1294
1295 private static async Task GetHistoryReport(object Sender, IqEventArgs e)
1296 {
1297 string TokenId = XML.Attribute(e.Query, "tokenId");
1298 if (string.IsNullOrEmpty(TokenId))
1299 {
1300 await e.IqErrorBadRequest(e.To, "Missing Token ID.", "en");
1301 return;
1302 }
1303
1304 CacheRecord Record = await GetCacheRecord(TokenId, e);
1305 if (Record is null)
1306 return;
1307
1308 // TODO: Avoid exporting during active change, to protect integrity of state object.
1309
1310 ReportFormat ReportFormat = XML.Attribute(e.Query, "format", ReportFormat.Markdown);
1311 StringBuilder Xml = new StringBuilder();
1312
1313 Xml.Append("<report xmlns='");
1314 Xml.Append(StateMachineNamespace);
1315 Xml.Append("'>");
1316 Xml.Append(XML.Encode(await GetHistoryReport(Record, ReportFormat)));
1317 Xml.Append("</report>");
1318
1319 await e.IqResult(Xml.ToString(), e.To);
1320 }
1321
1322 private static async Task<string> GetHistoryReport(CacheRecord Record, ReportFormat Format)
1323 {
1324 string Markdown = null;
1325
1326 foreach (IStateMachineNode Node in Record.Machine.Root.ChildNodes)
1327 {
1328 if (Node is ReportHistory Report)
1329 {
1330 Markdown = Report.Markdown;
1331 break;
1332 }
1333 }
1334
1335 if (string.IsNullOrEmpty(Markdown))
1336 return string.Empty;
1337
1338 Dictionary<string, SortedDictionary<DateTime, StateMachineSample>> ByVariableAndTime = new Dictionary<string, SortedDictionary<DateTime, StateMachineSample>>();
1339 SortedDictionary<DateTime, StateMachineSample> ByTime = null;
1340 IEnumerable<StateMachineSample> Samples = await Database.Find<StateMachineSample>(
1341 new FilterFieldEqualTo("StateMachineId", Record.Machine.StateMachineId.Value),
1342 "Variable", "Timestamp");
1343 string LastVariable = null;
1344 Variables Variables = Record.CurrentState.GetVariables(Record.Machine);
1345 DateTime Now = DateTime.UtcNow;
1346
1347 foreach (StateMachineSample Sample in Samples)
1348 {
1349 if (ByTime is null || Sample.Variable != LastVariable)
1350 {
1351 LastVariable = Sample.Variable;
1352
1353 if (!ByVariableAndTime.TryGetValue(LastVariable, out ByTime))
1354 {
1355 ByTime = new SortedDictionary<DateTime, StateMachineSample>(timestampDescending);
1356 ByVariableAndTime[LastVariable] = ByTime;
1357 }
1358 }
1359
1360 ByTime[Sample.Timestamp] = Sample;
1361 }
1362
1363 foreach (KeyValuePair<string, SortedDictionary<DateTime, StateMachineSample>> P in ByVariableAndTime)
1364 {
1365 if (Variables.TryGetVariable(P.Key, out Script.Variable v))
1366 {
1367 P.Value[Now] = new StateMachineSample()
1368 {
1369 StateMachineId = Record.Machine.StateMachineId,
1370 Variable = P.Key,
1371 Value = v.ValueObject,
1372 Timestamp = Now,
1373 Expires = Record.Machine.Expires,
1374 ArchiveOptional = Record.Machine.ArchiveOptional,
1375 ArchiveRequired = Record.Machine.ArchiveRequired
1376 };
1377 }
1378
1379 StateMachineSample[] History = new StateMachineSample[P.Value.Count];
1380 P.Value.Values.CopyTo(History, 0);
1381 Variables[P.Key] = History;
1382 }
1383
1384 return await NeuroFeaturesProcessor.FormatReport(Markdown, Format, Variables);
1385 }
1386
1387 private class TimestampDescending : IComparer<DateTime>
1388 {
1389 public int Compare(DateTime x, DateTime y)
1390 {
1391 return y.CompareTo(x);
1392 }
1393 }
1394
1395 private static readonly TimestampDescending timestampDescending = new TimestampDescending();
1396
1397 #endregion
1398
1399 #region GetStateDiagram
1400
1401 internal static async Task<string> GetStateDiagramAsync(Token Token, ReportFormat Format)
1402 {
1403 CacheRecord Record = await GetStateMachine(Token);
1404 if (Record is null)
1405 return string.Empty;
1406
1407 return await GetStateDiagram(Record, Format);
1408 }
1409
1410 private static async Task GetStateDiagram(object Sender, IqEventArgs e)
1411 {
1412 string TokenId = XML.Attribute(e.Query, "tokenId");
1413 if (string.IsNullOrEmpty(TokenId))
1414 {
1415 await e.IqErrorBadRequest(e.To, "Missing Token ID.", "en");
1416 return;
1417 }
1418
1419 CacheRecord Record = await GetCacheRecord(TokenId, e);
1420 if (Record is null)
1421 return;
1422
1423 // TODO: Avoid exporting during active change, to protect integrity of state object.
1424
1425 ReportFormat ReportFormat = XML.Attribute(e.Query, "format", ReportFormat.Markdown);
1426 StringBuilder Xml = new StringBuilder();
1427
1428 Xml.Append("<report xmlns='");
1429 Xml.Append(StateMachineNamespace);
1430 Xml.Append("'>");
1431 Xml.Append(XML.Encode(await GetStateDiagram(Record, ReportFormat)));
1432 Xml.Append("</report>");
1433
1434 await e.IqResult(Xml.ToString(), e.To);
1435 }
1436
1437 private static async Task<string> GetStateDiagram(CacheRecord Record, ReportFormat Format)
1438 {
1439 StringBuilder Markdown = new StringBuilder();
1440 StateMachine Machine = Record.Machine;
1441 Dictionary<string, string> States = new Dictionary<string, string>();
1442 LinkedList<State> StateNodes = new LinkedList<State>();
1443 string s;
1444 int AIndex = 0;
1445
1446 Markdown.AppendLine("```uml");
1447 Markdown.AppendLine("@startuml");
1448 Markdown.AppendLine();
1449 Markdown.AppendLine("skinparam state {");
1450 Markdown.AppendLine("\tFontSize 12");
1451 Markdown.AppendLine("\tFontStyle Italic");
1452 Markdown.AppendLine("\tAttributeFontSize 18");
1453 Markdown.AppendLine("\tBackgroundColor<<Action>> LightSalmon");
1454 Markdown.AppendLine("\tBackgroundColor<<Calc>> LightBlue");
1455 Markdown.AppendLine("\tBackgroundColor<<Current>> LightGreen");
1456 Markdown.AppendLine("\tFontStyle<<Current>> Bold, Italic");
1457 Markdown.AppendLine("\tAttributeFontStyle<<Current>> Bold");
1458 Markdown.AppendLine("\tBorderColor<<Current>> Black");
1459 Markdown.AppendLine("}");
1460 Markdown.AppendLine();
1461 Markdown.AppendLine("skinparam note {");
1462 Markdown.AppendLine("\tBackgroundColor<<Warning>> Salmon");
1463 Markdown.AppendLine("}");
1464 Markdown.AppendLine();
1465
1466 Markdown.AppendLine("state \"Initialize\" as Init");
1467 Markdown.AppendLine("[*] --> Init");
1468
1469 Markdown.Append("Init --> ");
1470 Markdown.AppendLine(GetStateLabel(States, Machine.Root.StartState));
1471
1472 foreach (IStateMachineNode Node in Machine.Root.ChildNodes)
1473 {
1474 if (Node is Model.Variable Variable)
1475 {
1476 Markdown.Append("Init : ");
1477 Markdown.Append(Variable.Id);
1478
1479 if (Record.CurrentState.TryGetVariable(Variable.Id, out CurrentStateVariable StateVariable) &&
1480 !string.IsNullOrEmpty(StateVariable.InitExpression))
1481 {
1482 Markdown.Append(":=");
1483 Markdown.Append(EncodeLabel(StateVariable.InitExpression));
1484 }
1485
1486 Markdown.AppendLine();
1487 }
1488 else if (Node is State State)
1489 {
1490 StateNodes.AddLast(State);
1491
1492 Markdown.AppendLine();
1493 Markdown.Append("state \"State\" as ");
1494 Markdown.Append(GetStateLabel(States, State.Id));
1495
1496 if (State.Id == Record.CurrentState.State)
1497 Markdown.Append(" <<Current>>");
1498
1499 Markdown.Append(" : ");
1500 Markdown.AppendLine(EncodeLabel(State.Id));
1501 }
1502 }
1503
1504 foreach (State State in StateNodes)
1505 {
1506 s = GetStateLabel(States, State.Id);
1507
1508 if (State.OnEvent is null)
1509 {
1510 Markdown.Append("note right of ");
1511 Markdown.Append(s);
1512 Markdown.AppendLine(" <<Warning>> : No events defined.");
1513 continue;
1514 }
1515
1516 foreach (OnEvent Event in State.OnEvent)
1517 GenerateEventStateDiagram(Event, Markdown, State, ref AIndex, s, States, Machine);
1518
1519 Markdown.AppendLine();
1520 }
1521
1522 Markdown.AppendLine("@enduml");
1523 Markdown.AppendLine("```");
1524
1525 return await NeuroFeaturesProcessor.FormatReport(Markdown.ToString(), Format, HttpServer.CreateVariables());
1526 }
1527
1528 private static void GenerateEventStateDiagram(OnEvent Event, StringBuilder Markdown,
1529 State State, ref int AIndex, string s, Dictionary<string, string> States,
1530 StateMachine Machine)
1531 {
1532 string s2;
1533 string s3 = Event.Event.Label;
1534
1535 if (!(State.OnLeave is null))
1536 {
1537 foreach (OnLeave OnLeave in State.OnLeave)
1538 {
1539 AIndex++;
1540 s2 = "A" + AIndex.ToString();
1541
1542 Markdown.Append("state \"OnLeave Action\" as ");
1543 Markdown.Append(s2);
1544 Markdown.Append(" <<Action>> : ");
1545 Markdown.AppendLine(EncodeLabel(OnLeave.ActionReferenceDefinition));
1546
1547 Markdown.Append(s);
1548 Markdown.Append(" --> ");
1549 Markdown.Append(s2);
1550
1551 if (!string.IsNullOrEmpty(s3))
1552 {
1553 Markdown.Append(" : ");
1554 Markdown.Append(EncodeLabel(s3));
1555 s3 = string.Empty;
1556 }
1557
1558 Markdown.AppendLine();
1559 s = s2;
1560 }
1561 }
1562
1563 if (Event.HasActionReference)
1564 {
1565 AIndex++;
1566 s2 = "A" + AIndex.ToString();
1567
1568 Markdown.Append("state \"Event Action\" as ");
1569 Markdown.Append(s2);
1570 Markdown.Append(" <<Action>> : ");
1571 Markdown.AppendLine(EncodeLabel(Event.ActionReferenceDefinition));
1572
1573 Markdown.Append(s);
1574 Markdown.Append(" --> ");
1575 Markdown.Append(s2);
1576
1577 if (!string.IsNullOrEmpty(s3))
1578 {
1579 Markdown.Append(" : ");
1580 Markdown.Append(EncodeLabel(s3));
1581 s3 = string.Empty;
1582 }
1583
1584 Markdown.AppendLine();
1585 s = s2;
1586
1587 if (Event.HasFailureState)
1588 s3 = "OK";
1589 }
1590 else if (Event.HasFailureState)
1591 {
1592 if (string.IsNullOrEmpty(s3))
1593 s3 = "OK";
1594 else
1595 s3 += " and OK";
1596 }
1597
1598 ActionReference[] Actions;
1599 bool Termination;
1600
1601 if (Event.HasNewState && Machine.TryGetState(Event.NewStateDefinition, out State NewState))
1602 {
1603 Actions = NewState.OnEnter;
1604 Termination = NewState.IsDone(Machine);
1605 }
1606 else
1607 {
1608 Actions = null;
1609 Termination = false;
1610 }
1611
1612 GenerateOnEnterBranch(Event, Markdown, State, ref AIndex, s, States,
1613 Actions, s3, Event.NewStateDefinition, Termination);
1614
1615 if (Event.HasFailureState)
1616 {
1617 if (Machine.TryGetState(Event.FailureStateDefinition, out State FailureState))
1618 {
1619 Actions = FailureState.OnEnter;
1620 Termination = FailureState.IsDone(Machine);
1621 }
1622 else
1623 {
1624 Actions = null;
1625 Termination = false;
1626 }
1627
1628 GenerateOnEnterBranch(Event, Markdown, State, ref AIndex, s, States,
1629 Actions, "Error", Event.FailureStateDefinition, Termination);
1630 }
1631 }
1632
1633 private static void GenerateOnEnterBranch(OnEvent Event, StringBuilder Markdown,
1634 State State, ref int AIndex, string s, Dictionary<string, string> States,
1635 ActionReference[] Actions, string s3, string NewState, bool Termination)
1636 {
1637 string s2;
1638
1639 if (!(Actions is null) && !Termination)
1640 {
1641 foreach (ActionReference Action in Actions)
1642 {
1643 AIndex++;
1644 s2 = "A" + AIndex.ToString();
1645
1646 Markdown.Append("state \"OnEnter Action\" as ");
1647 Markdown.Append(s2);
1648 Markdown.Append(" <<Action>> : ");
1649 Markdown.AppendLine(EncodeLabel(Action.ActionReferenceDefinition));
1650
1651 Markdown.Append(s);
1652 Markdown.Append(" --> ");
1653 Markdown.Append(s2);
1654
1655 if (!string.IsNullOrEmpty(s3))
1656 {
1657 Markdown.Append(" : ");
1658 Markdown.Append(EncodeLabel(s3));
1659 s3 = string.Empty;
1660 }
1661
1662 Markdown.AppendLine();
1663 s = s2;
1664 }
1665 }
1666
1667 if (Termination)
1668 s2 = "[*]";
1669 else
1670 {
1671 if (string.IsNullOrEmpty(NewState))
1672 NewState = State.Id;
1673
1674 bool VarState = !States.ContainsKey(NewState);
1675
1676 s2 = GetStateLabel(States, NewState);
1677
1678 if (VarState)
1679 {
1680 Markdown.Append("state \"Calc\" as ");
1681 Markdown.Append(s2);
1682 Markdown.Append(" <<Calc>> : ");
1683 Markdown.AppendLine(EncodeLabel(Event.NewStateDefinition));
1684
1685 Markdown.Append("note right of ");
1686 Markdown.Append(s2);
1687 Markdown.AppendLine(" <<Warning>> : Variable state, through script.");
1688 }
1689 }
1690
1691 Markdown.Append(s);
1692 Markdown.Append(" --> ");
1693 Markdown.Append(s2);
1694
1695 if (!string.IsNullOrEmpty(s3))
1696 {
1697 Markdown.Append(" : ");
1698 Markdown.Append(EncodeLabel(s3));
1699 }
1700
1701 Markdown.AppendLine();
1702 }
1703
1704
1705 private static string GetStateLabel(Dictionary<string, string> States, string StateId)
1706 {
1707 if (!States.TryGetValue(StateId, out string Id))
1708 {
1709 int Index = States.Count + 1;
1710 States[StateId] = Id = "S" + Index.ToString();
1711 }
1712
1713 return Id;
1714 }
1715
1716 private static string EncodeLabel(string s)
1717 {
1718 return s.
1719 Replace("\\", "\\\\").
1720 Replace("\"", "'").
1721 Replace("\a", "\\a").
1722 Replace("\b", "\\b").
1723 Replace("\f", "\\f").
1724 Replace("\n", "\\n").
1725 Replace("\r", "\\r").
1726 Replace("\t", "\\t").
1727 Replace("\v", "\\v");
1728 }
1729
1730 #endregion
1731
1732 #endregion
1733
1734 }
1735}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Definition: CommonTypes.cs:594
Helps with common JSON-related tasks.
Definition: JSON.cs:14
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:507
Contains a markdown document. This markdown document class supports original markdown,...
static string Encode(string s)
Encodes all special characters in a string so that it can be included in a markdown document without ...
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:914
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
Class representing an event.
Definition: Event.cs:10
Event(DateTime Timestamp, EventType Type, string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Class representing an event.
Definition: Event.cs:38
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
static void Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
Definition: Log.cs:682
Implements an HTTP server.
Definition: HttpServer.cs:36
static Variables CreateVariables()
Creates a new collection of variables, that contains access to the global set of variables.
Definition: HttpServer.cs:1604
bool IsComponentDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the component domain, or optionally, an alternative component domain.
Definition: Component.cs:123
Event arguments for IQ queries.
Definition: IqEventArgs.cs:12
Task IqResult(string Xml, string From)
Returns a response to the current request.
Definition: IqEventArgs.cs:113
Task IqErrorItemNotFound(XmppAddress From, string ErrorText, string Language)
Returns a item-not-found error.
Definition: IqEventArgs.cs:201
XmlElement Query
Query element, if found, null otherwise.
Definition: IqEventArgs.cs:70
XmppAddress To
To address attribute
Definition: IqEventArgs.cs:88
Task IqErrorServiceUnavailable(XmppAddress From, string ErrorText, string Language)
Returns a service-unavailable error.
Definition: IqEventArgs.cs:215
Task IqErrorBadRequest(XmppAddress From, string ErrorText, string Language)
Returns a bad-request error.
Definition: IqEventArgs.cs:159
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
bool IsBareJID
If the address is a Bare JID.
Definition: XmppAddress.cs:159
CaseInsensitiveString Domain
Domain
Definition: XmppAddress.cs:97
CaseInsensitiveString BareJid
Bare JID
Definition: XmppAddress.cs:45
CaseInsensitiveString Account
Account
Definition: XmppAddress.cs:124
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
string LowerCase
Lower-case representation of the case-insensitive string.
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static Task< IEnumerable< object > > FindDelete(string Collection, params string[] SortOrder)
Finds objects in a given collection and deletes them in the same atomic operation.
Definition: Database.cs:879
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:717
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field lesser than a given value.
Event arguments for database object events.
Implements an in-memory cache.
Definition: Cache.cs:15
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:14
static object Instantiate(Type Type, params object[] Arguments)
Returns an instance of the type Type . If one needs to be created, it is. If the constructor requires...
Definition: Types.cs:1353
static Type[] GetTypesImplementingInterface(string InterfaceFullName)
Gets all types implementing a given interface.
Definition: Types.cs:84
Class that keeps track of events and timing.
Definition: Profiler.cs:67
bool TryGetNote(int Index, out object Note)
Tries to get a note from the profile.
Definition: Profiler.cs:711
Profiler()
Class that keeps track of events and timing.
Definition: Profiler.cs:82
string ExportPlantUml(TimeUnit TimeUnit)
Exports events to PlantUML.
Definition: Profiler.cs:510
int NoteCount
Number of notes added.
Definition: Profiler.cs:695
void Start()
Starts measuring time.
Definition: Profiler.cs:200
Represents a named semaphore, i.e. an object, identified by a name, that allows single concurrent wri...
Definition: Semaphore.cs:19
Static class of application-wide semaphores that can be used to order access to editable objects.
Definition: Semaphores.cs:16
static async Task< Semaphore > BeginWrite(string Key)
Waits until the semaphore identified by Key is ready for writing. Each call to BeginWrite must be fo...
Definition: Semaphores.cs:90
Class managing a script expression.
Definition: Expression.cs:39
static string ToString(double Value)
Converts a value to a string, that can be parsed as part of an expression.
Definition: Expression.cs:4496
Contains information about a variable.
Definition: Variable.cs:10
object ValueObject
Object Value of variable.
Definition: Variable.cs:88
string Name
Name of variable.
Definition: Variable.cs:78
Collection of variables.
Definition: Variables.cs:25
virtual bool TryGetVariable(string Name, out Variable Variable)
Tries to get a variable object, given its name.
Definition: Variables.cs:52
Corresponds to a privilege in the system.
Definition: Privilege.cs:17
Corresponds to a user in the system.
Definition: User.cs:21
bool HasPrivilege(string Privilege)
If the user has a given privilege.
Definition: User.cs:129
Maintains the collection of all users in the system.
Definition: Users.cs:24
static async Task< User > GetUser(string UserName, bool CreateIfNew)
Gets the User object corresponding to a User Name.
Definition: Users.cs:65
Manages eDaler on accounts connected to the broker.
Abstract base class for eDaler URIs
Definition: EDalerUri.cs:20
CaseInsensitiveString ContractCondition
Optional Contract defining conditions that must be met before payment can be realized....
Definition: EDalerUri.cs:210
decimal TotalAmount
Total amount: Amount+AmountExtra
Definition: EDalerUri.cs:121
byte[] EncryptedMessage
Encrypted message for recipient. If EncryptionPublicKey is null, the message is just UTF-8 encoded.
Definition: EDalerUri.cs:197
byte[] EncryptionPublicKey
Sender public key used to generate the shared secret to encrypt the message for the recipient....
Definition: EDalerUri.cs:204
Event raised when a token has been destroyed.
Definition: Destroyed.cs:7
A text note logged on the token from an external source.
An xml note logged on the token from an external source.
An xml note logged on the token.
Definition: NoteXml.cs:9
Abstract base class for token events.
Definition: TokenEvent.cs:19
Event raised when a token has been transferred.
Definition: Transferred.cs:11
Marketplace processor, brokering sales of items via tenders and offers defined in smart contracts.
string Definition
M2M definition of token, in XML, from the original creation contract.
Definition: Token.cs:249
string DefinitionNamespace
Namespace of M2M definition of token.
Definition: Token.cs:258
CaseInsensitiveString TrustProviderJid
JID of Trust Provider, asserting claims in the token.
Definition: Token.cs:221
CaseInsensitiveString CreationContract
ID of contract that details the creation of the token.
Definition: Token.cs:267
CaseInsensitiveString MachineId
State Machine ID, if any
Definition: Token.cs:157
CaseInsensitiveString TrustProvider
Trust Provider, asserting claims in the token.
Definition: Token.cs:212
CaseInsensitiveString TokenId
Token ID
Definition: Token.cs:139
Class representing the current state of a state machine.
Definition: CurrentState.cs:20
bool IsRunning
If state-machine is running.
Definition: CurrentState.cs:57
CurrentStateVariable[] VariableValues
Current variable values.
Definition: CurrentState.cs:62
bool HasEnded
If state-machine has ended.
Definition: CurrentState.cs:52
DateTime Expires
When state-machine expires
Definition: CurrentState.cs:72
Variables GetVariables(StateMachine Machine)
Gets a new variable collection containing the current state variables.
string State
ID of current state in state-machine. Empty State = State-machine has ended.
Definition: CurrentState.cs:47
CurrentState()
Class representing the current state of a state machine.
Definition: CurrentState.cs:28
Class representing a persisted state-machine variable value.
static async Task< CachedEventHandler[]> GetEventHandlers(string EventType)
Gets registered event handlers of a given event type.
async Task RemoveFromCache()
Removes the event handler from the cache.
Abstract base class for persisted state-machine event handlers.
Definition: EventHandler.cs:18
string State
ID of state in state-machine to which the event handler belongs.
Definition: EventHandler.cs:55
int EventIndex
Zero-based index of event handler in state.
Definition: EventHandler.cs:60
Abstract base class for nodes referencing an action.
Contains information required for evaluating script in a state-machine.
StateMachine Machine
Reference to state-machine definition.
Event raised when an external text note has been logged on the token corresponding to the state-machi...
Event raised when an external XML note has been logged on the token corresponding to the state-machin...
Event raised when a text note has been logged on the token corresponding to the state-machine.
Definition: OnTextNote.cs:12
Event raised when the associated token is transferred to a new owner.
Event raised when an XML note has been logged on the token corresponding to the state-machine.
Definition: OnXmlNote.cs:17
Abstract base class for timed State-Machine event nodes.
async Task ReregisterOnStart(TimepointEventHandler Handler)
Re-registers the event on module start.
Contains a report over the history of the state-machine.
Definition: ReportHistory.cs:7
Contains a report over the present state of the state-machine.
Definition: ReportPresent.cs:7
IStateMachineNode[] ChildNodes
Child nodes, if available. Null if no children.
Action executed when entering a state.
Definition: OnEvent.cs:17
Action executed when entering a state.
Definition: OnLeave.cs:9
OnLeave[] OnLeave
Events raised when leaving the state.
Definition: State.cs:41
OnEvent[] OnEvent
Events that can be raised when in the state.
Definition: State.cs:46
Class representing a state machine.
Definition: StateMachine.cs:37
StateMachineRoot Root
Root of State-Machine model.
Definition: StateMachine.cs:61
bool TryGetState(string Id, out State State)
Tries to get a state.
Duration? ArchiveRequired
Duration after which token expires, the token is required to be archived.
Definition: StateMachine.cs:92
void IndexElements()
Indexes all elements in the state-machine.
async Task<(CurrentState, EvaluationArguments)> CreateCurrentState(Token Token, LegalComponent Legal, EDalerComponent EDaler, Profiler Profiler)
Starts processing of the state-machine.
static async Task GoToState(string StateId, EvaluationArguments Arguments)
Goes to a new state.
CaseInsensitiveString CreatorTokenId
ID of token that created the state-machine.
Definition: StateMachine.cs:56
async Task Start(EvaluationArguments Arguments)
Starts the processing of the state-machine.
string ObjectId
Object ID of state machine.
Definition: StateMachine.cs:46
CaseInsensitiveString StateMachineId
ID of State Machine.
Definition: StateMachine.cs:51
void CheckReferences(Token Token)
Indexes all elements in the state-machine.
static async Task EvaluationComplete(EvaluationArguments Arguments)
Method called when current evaluation has been completed, and new states need to be persisted.
DateTime Expires
When state-machine expires
Definition: StateMachine.cs:66
static async Task CheckConditionalEvents(EvaluationArguments Arguments)
Checks conditional events.
static async Task UnregisterEventHandlers(OnEvent[] Events, EvaluationArguments Arguments)
Unregisters event handlers for the current state.
static StateMachine Parse(XmlDocument Xml, Token Token)
Parses the XML representation of a State Machine.
static StateMachine Parse(Token Token)
Parses the XML representation of a State Machine.
const string StateMachineDefinition
Local name of state-machine definition
const string StateMachineNamespace
https://paiwise.tagroot.io/Schema/StateMachines.xsd
static StateMachine Parse(string Xml, Token Token)
Parses the XML representation of a State Machine.
static StateMachine Parse(XmlElement Xml, Token Token)
Parses the XML representation of a State Machine.
Class representing a sample of a state machine variable over time.
Basic interface for a user.
Definition: IUser.cs:7
IStateMachineNode[] ChildNodes
Child nodes, if available. Null if no children.
TimeUnit
Options for presenting time in reports.
Definition: Profiler.cs:16
ProfilerThreadType
Type of profiler thread.
ReportFormat
Desired report format
Definition: ReportFormat.cs:7
Represents a duration value, as defined by the xsd:duration data type: http://www....
Definition: Duration.cs:13