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;
3using System.Text;
4using System.Threading.Tasks;
5using System.Xml;
6using Waher.Content;
9using Waher.Events;
20using Waher.Script;
22using Waher.Security;
40
42{
46 public static class StateMachineProcessor
47 {
51 public const string StateMachineNamespace = "https://paiwise.tagroot.io/Schema/StateMachines.xsd";
52
56 public const string StateMachineDefinition = "StateMachine";
57
58 private static readonly Cache<CaseInsensitiveString, CacheRecord> stateMachines = new Cache<CaseInsensitiveString, CacheRecord>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromHours(1));
59 private static Dictionary<string, IStateMachineNode> stateMachineNodes = null;
60 private static LegalComponent legal;
61 private static EDalerComponent eDaler;
62
63 internal class CacheRecord
64 {
65 public StateMachine Machine;
67 public LegalComponent Legal;
69 public Profiler Profiler;
70 }
71
77 public static Task<StateMachine> Parse(Token Token)
78 {
80 }
81
88 public static async Task<StateMachine> Parse(string Xml, Token Token)
89 {
90 XmlDocument Doc = XML.ParseXml(Xml, true);
91
92 StateMachine Result = await Parse(Xml, Doc, Token);
93
94 Result.XmlDefinition = Xml;
95
96 return Result;
97 }
98
106 public static Task<StateMachine> Parse(string NormalizedXml, XmlDocument Xml, Token Token)
107 {
108 return Parse(NormalizedXml, Xml.DocumentElement, Token);
109 }
110
118 public static async Task<StateMachine> Parse(string NormalizedXml, XmlElement Xml, Token Token)
119 {
120 if (Xml is null || Xml.LocalName != "StateMachine" || Xml.NamespaceURI != StateMachineNamespace)
121 return null;
122
123 StateMachine Result = new StateMachine()
124 {
125 Root = (StateMachineRoot)await Create(Xml),
126 XmlDefinition = NormalizedXml,
127 DefinitionContractId = Token.CreationContract,
128 TrustProvider = Token.TrustProvider,
129 TrustProviderJid = Token.TrustProviderJid,
130 CreatorTokenId = Token.TokenId
131 };
132
133 Result.CheckReferences(Token);
134
135 return Result;
136 }
137
143 internal static bool IsStateMachineCreationContract(Contract Contract,
144 out string LocalName, out string Namespace)
145 {
146 return NeuroFeaturesProcessor.IsNeuroFeaturesCreationContract(Contract,
147 out LocalName, out Namespace) &&
148 LocalName == StateMachineDefinition &&
149 Namespace == StateMachineNamespace;
150 }
151
157 internal static async Task<IStateMachineNode> Create(XmlElement Xml)
158 {
160
161 if (stateMachineNodes is null)
162 {
163 Dictionary<string, IStateMachineNode> ByFqn = new Dictionary<string, IStateMachineNode>();
164
165 foreach (Type T in Types.GetTypesImplementingInterface(typeof(IStateMachineNode)))
166 {
167 if (T.IsAbstract || T.IsInterface || T.IsGenericTypeDefinition)
168 continue;
169
170 try
171 {
173 ByFqn[Node.Namespace + "#" + Node.LocalName] = Node;
174 }
175 catch (Exception ex)
176 {
177 Log.Exception(ex);
178 }
179 }
180
181 stateMachineNodes = ByFqn;
182 }
183
184 string Fqn = Xml.NamespaceURI + "#" + Xml.LocalName;
185 if (!stateMachineNodes.TryGetValue(Fqn, out Node))
186 throw new Exception("A State-Machine node named " + Fqn + " not known.");
187
188 Node = Node.Create();
189 await Node.Parse(Xml);
190
191 return Node;
192 }
193
199 internal static async Task EventGenerated(Token Token, TokenEvent Event)
200 {
202 return;
203
204 CacheRecord CacheRecord = await GetStateMachine(Token, false);
205 if (CacheRecord is null)
206 return;
207
208 await EventGenerated(Token, Event, CacheRecord);
209 }
210
211 private static async Task EventGenerated(Token Token, TokenEvent Event, CacheRecord CacheRecord)
212 {
213 StateMachine Machine = CacheRecord.Machine;
214
215 try
216 {
218 await TokenTransferred(Token, Transferred, Machine);
219 else if (Event is NoteText NoteText)
220 await NoteAdded(Token, NoteText, Machine);
221 else if (Event is NoteXml NoteXml)
222 await NoteAdded(Token, NoteXml, Machine);
224 await NoteAdded(Token, ExternalNoteText, Machine);
226 await NoteAdded(Token, ExternalNoteXml, Machine);
227 else if (Event is Destroyed)
228 await TokenDestroyed(Token, Machine);
229 }
230 catch (Exception ex)
231 {
232 if (!(CacheRecord.Profiler is null))
233 {
234 int NoteNr = CacheRecord.Profiler.AddNote(ex);
235 CacheRecord.Profiler?.Exception(ex, "Note" + NoteNr);
236 }
237
239 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.
240 }
241 }
242
248 internal static async Task EventGenerated(string StateMachineId, TokenEvent Event)
249 {
250 CacheRecord CacheRecord = await GetStateMachine(StateMachineId, false);
251 if (CacheRecord is null)
252 return;
253
254 Token Token = await NeuroFeaturesProcessor.GetToken(StateMachineId, true);
255
256 await EventGenerated(Token, Event, CacheRecord);
257 }
258
265 internal static Task<CacheRecord> GetStateMachine(Token Token, bool Locked)
266 {
267 return GetStateMachine(Token.MachineId, Locked);
268 }
269
277 internal static async Task<CurrentState> GetOrCreateCurrentState(StateMachine Machine, bool Locked)
278 {
280
281 if (Locked)
282 Semaphore = null;
283 else
284 Semaphore = await Semaphores.BeginWrite("machine:" + Machine.StateMachineId.Value);
285
286 try
287 {
288 CurrentState CurrentState = await Database.FindFirstIgnoreRest<CurrentState>(new FilterFieldEqualTo("StateMachineId", Machine.StateMachineId));
289
290 if (CurrentState is null)
291 {
293 {
294 StateMachineId = Machine.StateMachineId,
295 VariableValues = Array.Empty<CurrentStateVariable>(),
296 State = string.Empty,
297 Expires = Machine.Expires,
298 ArchiveRequired = Machine.ArchiveRequired,
299 ArchiveOptional = Machine.ArchiveOptional
300 };
301
303 }
304
305 return CurrentState;
306 }
307 finally
308 {
309 if (!(Semaphore is null))
310 await Semaphore.DisposeAsync();
311 }
312 }
313
320 internal static async Task<CacheRecord> GetStateMachine(string MachineId, bool Locked)
321 {
322 if (string.IsNullOrEmpty(MachineId))
323 return null;
324
325 if (stateMachines.TryGetValue(MachineId, out CacheRecord Result))
326 return Result;
327
328 StateMachine Machine = await Database.FindFirstIgnoreRest<StateMachine>(new FilterFieldEqualTo("StateMachineId", MachineId));
329 if (Machine is null)
330 return null;
331
332 Machine.IndexElements();
333
334 CurrentState CurrentState = await GetOrCreateCurrentState(Machine, Locked);
335
336 Result = new CacheRecord()
337 {
338 Machine = Machine,
340 EDaler = eDaler,
341 Legal = legal,
342 Profiler = new Profiler(MachineId, ProfilerThreadType.StateMachine)
343 };
344
345 stateMachines[MachineId] = Result;
346 Result.Profiler.NewState(CurrentState.State);
347 Result.Profiler.Start();
348 Result.Profiler.Event("Reloaded");
349
350 return Result;
351 }
352
358 internal static async Task<string> GetCurrentState(string MachineId)
359 {
360 CacheRecord State = await GetStateMachine(MachineId, false);
361 return State?.CurrentState?.State;
362 }
363
369 internal static async Task ModuleStarted(LegalComponent Legal, EDalerComponent EDaler)
370 {
371 legal = Legal;
372 eDaler = EDaler;
373
374 Ledger.EntryAdded += Ledger_EntryAdded;
375 Ledger.EntryUpdated += Ledger_EntryUpdated;
376 Ledger.EntryDeleted += Ledger_EntryDeleted;
377
378 LinkedList<TimepointEventHandler> ToDelete = null;
379
380 try
381 {
382 foreach (TimepointEventHandler Handler in await Database.Find<TimepointEventHandler>(
383 new FilterFieldEqualTo("EventType", nameof(TimepointEventHandler))))
384 {
385 if (await ReregisterOnStart(Handler))
386 {
387 ToDelete ??= new LinkedList<TimepointEventHandler>();
388 ToDelete.AddLast(Handler);
389 }
390 }
391 }
392 finally
393 {
394 if (!(ToDelete is null))
395 await Database.Delete(ToDelete);
396
397 await ScheduledAction.ScheduleNext();
398 }
399 }
400
406 private static async Task<bool> ReregisterOnStart(TimepointEventHandler Handler)
407 {
408 try
409 {
410 CacheRecord CacheRecord = await GetStateMachine(Handler.StateMachineId, false);
411 if (CacheRecord is null)
412 return true;
413
414 StateMachine Machine = CacheRecord.Machine;
416 if (Handler.State != CurrentState.State)
417 return true;
418
419 if (!Machine.TryGetState(Handler.State, out State State))
420 return true;
421
422 if (State.OnEvent is null || State.OnEvent.Length <= Handler.EventIndex)
423 return true;
424
427 return true;
428
429 await TimedEventNode.ReregisterOnStart(Handler);
430 return false;
431 }
432 catch (Exception ex)
433 {
434 Log.Exception(ex);
435 return true;
436 }
437 }
438
442 internal static Task ModuleStopped()
443 {
444 legal = null;
445 eDaler = null;
446
447 Ledger.EntryAdded -= Ledger_EntryAdded;
448 Ledger.EntryUpdated -= Ledger_EntryUpdated;
449 Ledger.EntryDeleted -= Ledger_EntryDeleted;
450
451 TimedEventNode.ClearPendingEvents();
452
453 return Task.CompletedTask;
454 }
455
463 internal static async Task Start(StateMachine Machine, Token Token, LegalComponent Legal, EDalerComponent EDaler)
464 {
465 try
466 {
467 Profiler Profiler = new Profiler(Machine.StateMachineId, ProfilerThreadType.StateMachine);
468 Profiler.Start();
469
471
472 stateMachines[Machine.StateMachineId] = new CacheRecord()
473 {
475 Machine = Machine,
476 EDaler = EDaler,
477 Legal = Legal,
479 };
480
481 await Machine.Start(Arguments);
482 }
483 catch (Exception ex)
484 {
485 Log.Exception(ex, Machine.ObjectId,
486 new KeyValuePair<string, object>("StateMachineId", Machine.StateMachineId.Value));
487 }
488 }
489
490 internal static void EventRaised(EventHandlers.EventHandler Handler, Token Token,
491 params KeyValuePair<string, object>[] ToSet)
492 {
493 Task.Run(async () =>
494 {
495 try
496 {
497 await ProcessEvent(Handler, Token, ToSet);
498 }
499 catch (Exception ex)
500 {
501 Log.Exception(ex);
502 }
503 });
504 }
505
506 internal static async Task<EventRecord> ProcessEvent(EventHandlers.EventHandler Handler,
507 Token Token, params KeyValuePair<string, object>[] ToSet)
508 {
509 using Semaphore Semaphore = await Semaphores.BeginWrite("machine:" + Handler.StateMachineId);
510 return await EventRaisedLocked(Handler, Token, ToSet);
511 }
512
513 internal class EventRecord
514 {
515 public CacheRecord CacheRecord;
516 public bool SuppressSamples;
517 }
518
519 internal static async Task<EventRecord> EventRaisedLocked(EventHandlers.EventHandler Handler,
520 Token Token, params KeyValuePair<string, object>[] ToSet)
521 {
522 EventRecord Result = new EventRecord()
523 {
524 SuppressSamples = false,
525 CacheRecord = await GetStateMachine(Handler.StateMachineId, true)
526 };
527
528 if (Result.CacheRecord is null)
529 return null;
530
531 try
532 {
533 StateMachine Machine = Result.CacheRecord.Machine;
534 CurrentState CurrentState = Result.CacheRecord.CurrentState;
535 if (Handler.State != CurrentState.State)
536 return null;
537
538 if (!Machine.TryGetState(Handler.State, out State State))
539 return null;
540
541 if (State.OnEvent is null || State.OnEvent.Length <= Handler.EventIndex)
542 return null;
543
545
546 if (!(ToSet is null))
547 {
548 foreach (KeyValuePair<string, object> P in ToSet)
549 Variables[P.Key] = P.Value;
550 }
551
553 Token = await NeuroFeaturesProcessor.GetToken(Machine.CreatorTokenId, false);
554
555 EvaluationArguments Arguments = new EvaluationArguments(Variables, Machine,
556 Token, CurrentState, Result.CacheRecord.Legal ?? legal,
557 Result.CacheRecord.EDaler ?? eDaler, Result.CacheRecord.Profiler);
558
559 try
560 {
562 string StateId;
563
564 if (await Event.Applies(Arguments))
565 {
566 try
567 {
568 Result.SuppressSamples = await Event.GetSuppressSample(Arguments);
569 await Event.ExecuteLog(Arguments, Result.SuppressSamples);
570 StateId = await Event.GetNewState(Arguments);
571 }
572 catch (Exception ex)
573 {
574 StateId = await Event.GetFailureState(Arguments);
575
576 if (string.IsNullOrEmpty(StateId))
577 Log.Error(ex, Arguments.Machine.ObjectId);
578 }
579
580 if (string.IsNullOrEmpty(StateId))
581 await StateMachine.CheckConditionalEvents(Arguments);
582 else
583 await StateMachine.GoToState(StateId, Arguments);
584 }
585 }
586 finally
587 {
588 await StateMachine.EvaluationComplete(Arguments);
589 }
590 }
591 catch (Exception ex)
592 {
593 if (!(Result.CacheRecord.Profiler is null))
594 {
595 int NoteNr = Result.CacheRecord.Profiler.AddNote(ex);
596 Result.CacheRecord.Profiler?.Exception(ex, "Note" + NoteNr);
597 }
598
599 Log.Exception(ex, Handler.StateMachineId);
600 }
601
602 return Result;
603 }
604
611 internal static async Task NoteAdded(Token Token, NoteText Event, StateMachine Machine)
612 {
613 using Semaphore Semaphore = await Semaphores.BeginWrite("machine:" + Machine.StateMachineId.Value);
614
616 nameof(TokenNoteEventHandler) + "|" + nameof(OnTextNote) + "|" +
617 Machine.StateMachineId + "||");
618
619 if ((Handlers?.Length ?? 0) == 0)
620 return;
621
622 foreach (CachedEventHandler Handler in Handlers)
623 {
625 {
626 KeyValuePair<string, object>[] Variables = GetVariablesToSet(
627 TokenNoteEventHandler, Event.Note, Event.Personal, null);
628
629 await EventRaisedLocked(Handler, Token, Variables);
630 }
631 }
632 }
633
640 internal static async Task NoteAdded(Token Token, NoteXml Event, StateMachine Machine)
641 {
642 using Semaphore Semaphore = await Semaphores.BeginWrite("machine:" + Machine.StateMachineId.Value);
643
645 nameof(TokenNoteEventHandler) + "|" + nameof(OnXmlNote) + "|" +
646 Machine.StateMachineId + "|" + Event.LocalName + "|" + Event.Namespace);
647
648 if ((Handlers?.Length ?? 0) == 0)
649 return;
650
651 XmlDocument Doc = null;
652
653 foreach (CachedEventHandler Handler in Handlers)
654 {
656 {
657 Doc ??= XML.ParseXml(Event.Note);
658
659 await EventRaisedLocked(Handler, Token, GetVariablesToSet(TokenNoteEventHandler, Doc, Event.Personal, null));
660 }
661 }
662 }
663
670 internal static async Task NoteAdded(Token Token, ExternalNoteText Event, StateMachine Machine)
671 {
672 using Semaphore Semaphore = await Semaphores.BeginWrite("machine:" + Machine.StateMachineId.Value);
673
675 nameof(TokenNoteEventHandler) + "|" + nameof(OnExternalTextNote) + "|" +
676 Machine.StateMachineId + "||");
677
678 if ((Handlers?.Length ?? 0) == 0)
679 return;
680
681
682 foreach (CachedEventHandler Handler in Handlers)
683 {
685 {
686 if (!await IsAuthorized(Event.Source, TokenNoteEventHandler.Privilege))
687 continue;
688
689 await EventRaisedLocked(Handler, Token, GetVariablesToSet(TokenNoteEventHandler, Event.Note, Event.Personal, Event.Source));
690 }
691 }
692 }
693
700 internal static async Task NoteAdded(Token Token, ExternalNoteXml Event, StateMachine Machine)
701 {
702 using Semaphore Semaphore = await Semaphores.BeginWrite("machine:" + Machine.StateMachineId.Value);
703
705 nameof(TokenNoteEventHandler) + "|" + nameof(OnExternalXmlNote) + "|" +
706 Machine.StateMachineId + "|" + Event.LocalName + "|" + Event.Namespace);
707
708 if ((Handlers?.Length ?? 0) == 0)
709 return;
710
711 XmlDocument Doc = null;
712
713 foreach (CachedEventHandler Handler in Handlers)
714 {
716 {
717 if (!await IsAuthorized(Event.Source, TokenNoteEventHandler.Privilege))
718 continue;
719
720 Doc ??= XML.ParseXml(Event.Note);
721
722 await EventRaisedLocked(Handler, Token, GetVariablesToSet(TokenNoteEventHandler, Doc, Event.Personal, Event.Source));
723 }
724 }
725 }
726
727 private static async Task<bool> IsAuthorized(string Source, string Privilege)
728 {
729 bool CheckPrivilege = !string.IsNullOrEmpty(Privilege);
730
731 if (!CheckPrivilege && Source.IndexOf('@') > 0)
732 return true;
733
734 IUser User = await Users.GetUser(Source, false);
735 if (User is null)
736 return false;
737
739 }
740
741 internal static KeyValuePair<string, object>[] GetVariablesToSet(TokenNoteEventHandler Handler, object Note, bool Personal, string Source)
742 {
743 List<KeyValuePair<string, object>> Result = new List<KeyValuePair<string, object>>();
744
745 if (!string.IsNullOrEmpty(Handler.SourceVariable))
746 Result.Add(new KeyValuePair<string, object>(Handler.SourceVariable, Source));
747
748 if (!string.IsNullOrEmpty(Handler.PersonalVariable))
749 Result.Add(new KeyValuePair<string, object>(Handler.PersonalVariable, Personal));
750
751 if (!string.IsNullOrEmpty(Handler.NoteVariable))
752 Result.Add(new KeyValuePair<string, object>(Handler.NoteVariable, Note));
753
754 return Result.ToArray();
755 }
756
763 internal static async Task TokenTransferred(Token Token, Transferred Event, StateMachine Machine)
764 {
765 using Semaphore Semaphore = await Semaphores.BeginWrite("machine:" + Machine.StateMachineId.Value);
766
768 nameof(TokenTransferredEventHandler) + "|" + nameof(OnTransferred) + "|" +
769 Machine.StateMachineId);
770
771 if ((Handlers?.Length ?? 0) == 0)
772 return;
773
774 foreach (CachedEventHandler Handler in Handlers)
775 {
777 await EventRaisedLocked(Handler, Token, GetVariablesToSet(TokenTransferredEventHandler, Event));
778 }
779 }
780
781 internal static KeyValuePair<string, object>[] GetVariablesToSet(
783 {
784 List<KeyValuePair<string, object>> Result = new List<KeyValuePair<string, object>>();
785
786 if (!string.IsNullOrEmpty(Handler.SellerVariable))
787 Result.Add(new KeyValuePair<string, object>(Handler.SellerVariable, Event.Seller));
788
789 if (!string.IsNullOrEmpty(Handler.BuyerVariable))
790 Result.Add(new KeyValuePair<string, object>(Handler.BuyerVariable, Event.Owner));
791
792 if (!string.IsNullOrEmpty(Handler.ContractVariable))
793 Result.Add(new KeyValuePair<string, object>(Handler.ContractVariable, Event.OwnershipContract));
794
795 if (!string.IsNullOrEmpty(Handler.ValueVariable))
796 Result.Add(new KeyValuePair<string, object>(Handler.ValueVariable, Event.Value));
797
798 if (!string.IsNullOrEmpty(Handler.AmountVariable))
799 Result.Add(new KeyValuePair<string, object>(Handler.AmountVariable, Event.Value - Event.Commission));
800
801 if (!string.IsNullOrEmpty(Handler.CurrencyVariable))
802 Result.Add(new KeyValuePair<string, object>(Handler.CurrencyVariable, Event.Currency));
803
804 return Result.ToArray();
805 }
806
812 internal static async Task EDalerSent(XmppAddress Owner, EDalerUri Uri)
813 {
815 nameof(OwnerEventHandler) + "|" + nameof(OnPaymentSent) + "|" + Owner.BareJid.LowerCase);
816
817 if ((Handlers?.Length ?? 0) == 0)
818 return;
819
820 foreach (CachedEventHandler Handler in Handlers)
821 {
822 if (Handler is PaymentEventHandler PaymentHandler)
823 EventRaised(Handler, null, GetVariablesToSet(PaymentHandler, Uri, Uri.To.BareJid));
824 }
825 }
826
833 internal static async Task EDalerReceived(XmppAddress Owner, EDalerUri Uri, string Sender)
834 {
836 nameof(OwnerEventHandler) + "|" + nameof(OnPaymentReceived) + "|" + Owner.BareJid.LowerCase);
837
838 if ((Handlers?.Length ?? 0) == 0)
839 return;
840
841 XmppAddress SenderAddress = new XmppAddress(Sender);
842 Sender = SenderAddress.BareJid;
843
844 foreach (CachedEventHandler Handler in Handlers)
845 {
846 if (Handler is PaymentEventHandler PaymentHandler)
847 EventRaised(Handler, null, GetVariablesToSet(PaymentHandler, Uri, Sender));
848 }
849 }
850
851 internal static KeyValuePair<string, object>[] GetVariablesToSet(PaymentEventHandler Handler, EDalerUri Uri, string RemoteAddress)
852 {
853 List<KeyValuePair<string, object>> Result = new List<KeyValuePair<string, object>>();
854
855 if (!string.IsNullOrEmpty(Handler.RemoteVariable))
856 Result.Add(new KeyValuePair<string, object>(Handler.RemoteVariable, RemoteAddress));
857
858 if (!string.IsNullOrEmpty(Handler.AmountVariable))
859 Result.Add(new KeyValuePair<string, object>(Handler.AmountVariable, (double)Uri.Amount));
860
861 if (!string.IsNullOrEmpty(Handler.AmountExtraVariable))
862 Result.Add(new KeyValuePair<string, object>(Handler.AmountExtraVariable, (double)(Uri.AmountExtra ?? 0M)));
863
864 if (!string.IsNullOrEmpty(Handler.AmountTotalVariable))
865 Result.Add(new KeyValuePair<string, object>(Handler.AmountTotalVariable, (double)Uri.TotalAmount));
866
867 if (!string.IsNullOrEmpty(Handler.CurrencyVariable))
868 Result.Add(new KeyValuePair<string, object>(Handler.CurrencyVariable, Uri.Currency));
869
870 if (!string.IsNullOrEmpty(Handler.ReferenceVariable))
871 {
872 if (Uri.EncryptionPublicKey is null && !(Uri.EncryptedMessage is null))
873 Result.Add(new KeyValuePair<string, object>(Handler.ReferenceVariable, Encoding.UTF8.GetString(Uri.EncryptedMessage)));
874 else
875 Result.Add(new KeyValuePair<string, object>(Handler.ReferenceVariable, null));
876 }
877
878 if (!string.IsNullOrEmpty(Handler.ConditionVariable))
879 Result.Add(new KeyValuePair<string, object>(Handler.ConditionVariable, Uri.ContractCondition?.Value));
880
881 return Result.ToArray();
882 }
883
890 internal static async Task ContractSignature(Contract Contract, string Role,
891 CaseInsensitiveString LegalId)
892 {
893 StringBuilder sb = new StringBuilder();
894
895 sb.Append(Contract.ContractId.LowerCase);
896 sb.Append(' ');
897 sb.Append(Role);
898 sb.Append(' ');
899 sb.Append(LegalId.LowerCase);
900
901 SHA3_256 H = new SHA3_256();
902 byte[] Hash = H.ComputeVariable(Encoding.UTF8.GetBytes(sb.ToString()));
903
904 if (!await PersistedHashes.AddHash("ContractSignature", Contract.Expires, Hash))
905 return;
906
907 sb.Clear();
908
909 sb.Append(nameof(ContractEventHandler));
910 sb.Append('|');
911 sb.Append(nameof(OnContractSignature));
912 sb.Append('|');
913 sb.Append(Contract.ForMachinesLocalName);
914 sb.Append('|');
915 sb.Append(Contract.ForMachinesNamespace);
916
917 CachedEventHandler[] Handlers = await CachedEventHandler.GetEventHandlers(sb.ToString());
918
919 if ((Handlers?.Length ?? 0) > 0)
920 {
921 foreach (CachedEventHandler Handler in Handlers)
922 {
924 {
925 EventRaised(Handler, null, await GetVariablesToSet(ContractEventHandler,
926 Contract, LegalId, Role));
927 }
928 }
929 }
930
931 if (LegalComponent.HasSecondaryName(Contract, out string LocalName,
932 out string Namespace, out SecondaryNameType NameType))
933 {
934 sb.Clear();
935
936 sb.Append(nameof(ContractEventHandler));
937 sb.Append('|');
938 sb.Append(nameof(OnContractSignature));
939 sb.Append('|');
940 sb.Append(LocalName);
941 sb.Append('|');
942 sb.Append(Namespace);
943 sb.Append('|');
944 sb.Append(NameType.ToString());
945
946 Handlers = await CachedEventHandler.GetEventHandlers(sb.ToString());
947
948 if ((Handlers?.Length ?? 0) > 0)
949 {
950 foreach (CachedEventHandler Handler in Handlers)
951 {
953 {
954 EventRaised(Handler, null, await GetVariablesToSet(ContractEventHandler,
955 Contract, LegalId, Role));
956 }
957 }
958 }
959 }
960 }
961
966 internal static async Task ContractSigned(Contract Contract)
967 {
968 SHA3_256 H = new SHA3_256();
969 byte[] Hash = H.ComputeVariable(Encoding.UTF8.GetBytes(Contract.ContractId.LowerCase));
970
971 if (!await PersistedHashes.AddHash("ContractSigned", Contract.Expires, Hash))
972 return;
973
974 StringBuilder sb = new StringBuilder();
975
976 sb.Append(nameof(ContractEventHandler));
977 sb.Append('|');
978 sb.Append(nameof(OnContractSigned));
979 sb.Append('|');
980 sb.Append(Contract.ForMachinesLocalName);
981 sb.Append('|');
982 sb.Append(Contract.ForMachinesNamespace);
983
984 CachedEventHandler[] Handlers = await CachedEventHandler.GetEventHandlers(sb.ToString());
985
986 if ((Handlers?.Length ?? 0) > 0)
987 {
988 foreach (CachedEventHandler Handler in Handlers)
989 {
991 {
992 EventRaised(Handler, null, await GetVariablesToSet(ContractEventHandler,
993 Contract, null, null));
994 }
995 }
996 }
997
998 if (LegalComponent.HasSecondaryName(Contract, out string LocalName,
999 out string Namespace, out SecondaryNameType NameType))
1000 {
1001 sb.Clear();
1002
1003 sb.Append(nameof(ContractEventHandler));
1004 sb.Append('|');
1005 sb.Append(nameof(OnContractSigned));
1006 sb.Append('|');
1007 sb.Append(LocalName);
1008 sb.Append('|');
1009 sb.Append(Namespace);
1010 sb.Append('|');
1011 sb.Append(NameType.ToString());
1012
1013 Handlers = await CachedEventHandler.GetEventHandlers(sb.ToString());
1014
1015 if ((Handlers?.Length ?? 0) > 0)
1016 {
1017 foreach (CachedEventHandler Handler in Handlers)
1018 {
1020 {
1021 EventRaised(Handler, null, await GetVariablesToSet(ContractEventHandler,
1022 Contract, null, null));
1023 }
1024 }
1025 }
1026 }
1027 }
1028
1033 internal static async Task ContractTemplateApproved(Contract Contract)
1034 {
1035 StringBuilder sb = new StringBuilder();
1036
1037 sb.Append(nameof(ContractEventHandler));
1038 sb.Append('|');
1039 sb.Append(nameof(OnContractTemplateApproved));
1040 sb.Append('|');
1041 sb.Append(Contract.ForMachinesLocalName);
1042 sb.Append('|');
1043 sb.Append(Contract.ForMachinesNamespace);
1044
1045 CachedEventHandler[] Handlers = await CachedEventHandler.GetEventHandlers(sb.ToString());
1046
1047 if ((Handlers?.Length ?? 0) > 0)
1048 {
1049 foreach (CachedEventHandler Handler in Handlers)
1050 {
1052 {
1053 EventRaised(Handler, null, await GetVariablesToSet(ContractEventHandler,
1054 Contract, null, null));
1055 }
1056 }
1057 }
1058
1059 if (LegalComponent.HasSecondaryName(Contract, out string LocalName,
1060 out string Namespace, out SecondaryNameType NameType))
1061 {
1062 sb.Clear();
1063
1064 sb.Append(nameof(ContractEventHandler));
1065 sb.Append('|');
1066 sb.Append(nameof(OnContractTemplateApproved));
1067 sb.Append('|');
1068 sb.Append(LocalName);
1069 sb.Append('|');
1070 sb.Append(Namespace);
1071 sb.Append('|');
1072 sb.Append(NameType.ToString());
1073
1074 Handlers = await CachedEventHandler.GetEventHandlers(sb.ToString());
1075
1076 if ((Handlers?.Length ?? 0) > 0)
1077 {
1078 foreach (CachedEventHandler Handler in Handlers)
1079 {
1081 {
1082 EventRaised(Handler, null, await GetVariablesToSet(ContractEventHandler,
1083 Contract, null, null));
1084 }
1085 }
1086 }
1087 }
1088 }
1089
1090 internal static async Task<KeyValuePair<string, object>[]> GetVariablesToSet(ContractEventHandler Handler,
1092 {
1093 List<KeyValuePair<string, object>> Result = new List<KeyValuePair<string, object>>();
1094
1095 if (!string.IsNullOrEmpty(Handler.ContractIdVariable))
1096 Result.Add(new KeyValuePair<string, object>(Handler.ContractIdVariable, Contract.ContractId.Value));
1097
1098 if (!string.IsNullOrEmpty(Handler.ContractXmlVariable))
1099 {
1100 StringBuilder sb = new StringBuilder();
1101 await Contract.Serialize(sb, true, true, true, true, true, true, true, null, legal);
1102
1103 XmlDocument Doc = XML.ParseXml(sb.ToString(), true);
1104
1105 Result.Add(new KeyValuePair<string, object>(Handler.ContractXmlVariable, Doc));
1106 }
1107
1108 if (!string.IsNullOrEmpty(Handler.ParametersVariable))
1109 {
1110 Dictionary<string, object> Parameters = new Dictionary<string, object>()
1111 {
1112 { "Duration", Contract.Duration }
1113 };
1114
1115 if (Contract.FirstSignatureAt.HasValue)
1116 {
1117 Parameters["Now"] = Contract.FirstSignatureAt.Value.ToLocalTime();
1118 Parameters["NowUtc"] = Contract.FirstSignatureAt.Value.ToUniversalTime();
1119 }
1120
1121 foreach (Parameter P in Contract.Parameters)
1122 Parameters[P.Name] = P.ObjectValue;
1123
1124 Result.Add(new KeyValuePair<string, object>(Handler.ParametersVariable, Parameters));
1125 }
1126
1127 if (!string.IsNullOrEmpty(Handler.RolesVariable) && !(Contract.ClientSignatures is null))
1128 {
1129 Dictionary<string, ChunkedList<string>> Roles = new Dictionary<string, ChunkedList<string>>();
1130
1131 foreach (ClientSignature Signature in Contract.ClientSignatures)
1132 {
1133 if (!Roles.TryGetValue(Signature.Role, out ChunkedList<string> List))
1134 {
1135 List = new ChunkedList<string>();
1136 Roles[Signature.Role] = List;
1137 }
1138
1139 List.Add(Signature.LegalId.LowerCase);
1140 }
1141
1142 Dictionary<string, object> Roles2 = new Dictionary<string, object>();
1143
1144 foreach (KeyValuePair<string, ChunkedList<string>> P in Roles)
1145 Roles2[P.Key] = P.Value.ToArray();
1146
1147 Result.Add(new KeyValuePair<string, object>(Handler.RolesVariable, Roles2));
1148 }
1149
1150 if (!string.IsNullOrEmpty(Handler.MachineReadableVariable))
1151 {
1152 Result.Add(new KeyValuePair<string, object>(Handler.MachineReadableVariable,
1153 Contract.ForMachinesParsed));
1154 }
1155
1156 if (!string.IsNullOrEmpty(Handler.LegalIdVariable))
1157 Result.Add(new KeyValuePair<string, object>(Handler.LegalIdVariable, LegalId?.Value));
1158
1159 if (!string.IsNullOrEmpty(Handler.RoleVariable))
1160 Result.Add(new KeyValuePair<string, object>(Handler.RoleVariable, Role));
1161
1162 return Result.ToArray();
1163 }
1164
1170 internal static async Task TokenDestroyed(Token Token, StateMachine Machine)
1171 {
1172 using Semaphore Semaphore = await Semaphores.BeginWrite("machine:" + Machine.StateMachineId.Value);
1173
1174 CacheRecord Record = await GetStateMachine(Machine.StateMachineId.Value, true);
1175 if (Record is null)
1176 return;
1177
1178 Variables Variables = Record.CurrentState.GetVariables(Machine);
1180 Machine, Token, Record.CurrentState, Record.Legal, Record.EDaler,
1181 Record.Profiler);
1182
1184 nameof(TokenEventHandler) + "|" + nameof(OnDestroyed) + "|" +
1185 Machine.StateMachineId);
1186
1187 if ((Handlers?.Length ?? 0) > 0)
1188 {
1189 foreach (CachedEventHandler Handler in Handlers)
1190 {
1191 if (Handler is TokenEventHandler TokenEventHandler)
1192 await EventRaisedLocked(Handler, Token);
1193 }
1194 }
1195
1196 Token Token2 = await Database.FindFirstIgnoreRest<Token>(
1197 new FilterFieldEqualTo("MachineId", Machine.StateMachineId));
1198
1199 if (Token2 is null)
1200 await StateMachine.GoToState(string.Empty, Arguments);
1201 }
1202
1219 internal static async Task<int> KillMachine(Token Token, IUser User,
1220 CaseInsensitiveString LegalId, string RemoteEndPoint,
1221 EventHandlerAsync<QuickLoginResponseEventArgs> Callback, object State)
1222 {
1224 return 0;
1225
1226 CacheRecord Record = await GetStateMachine(Token.MachineId, false);
1227 if (Record is null)
1228 return 1;
1229
1230 string TokenId = Token.TokenId;
1231 StringBuilder sb = new StringBuilder();
1232 DateTime TP = DateTime.UtcNow;
1233
1234 sb.Append("To kill a state-machine. By signing this request, you ");
1235 sb.Append("confirm you have reviewed the token (");
1236 sb.Append(TokenId);
1237 sb.Append(") and understand the consequences of killing the state-machine. ");
1238 sb.Append("You also agree to store your approval and digital signature in ");
1239 sb.Append("the event log, and take full responsibility of the killing of the ");
1240 sb.Append("state-machine. This petition was sent ");
1241 sb.Append(TP.ToShortDateString());
1242 sb.Append(" at ");
1243 sb.Append(TP.ToLongTimeString());
1244 sb.Append(" UTC.");
1245
1246 string Purpose = sb.ToString();
1247 byte[] Content = Hashes.ComputeSHA256Hash(Encoding.UTF8.GetBytes(Purpose));
1248
1249 await QuickLogin.StartQuickLogin(LegalId, Purpose, Content, null, null, RemoteEndPoint,
1250 async (Sender, e) =>
1251 {
1252 if (e.Response)
1253 {
1254 Variables Variables = Record.CurrentState.GetVariables(Record.Machine);
1255 EvaluationArguments Arguments = new EvaluationArguments(Variables,
1256 Record.Machine, Token, Record.CurrentState, Record.Legal, Record.EDaler,
1257 Record.Profiler);
1258
1259 using Semaphore Semaphore = await Semaphores.BeginWrite("machine:" + Token.MachineId.Value);
1260
1261 await StateMachine.GoToState(string.Empty, Arguments);
1262
1263 StringBuilder sb = new StringBuilder();
1264 e.RequestedIdentity.Serialize(sb, true, true, true, true, true, true, true);
1265
1266 NoteText Comment = new NoteText()
1267 {
1268 ArchiveOptional = Token.ArchiveOptional,
1269 ArchiveRequired = Token.ArchiveRequired,
1270 Expires = Token.Expires,
1271 TokenId = TokenId,
1272 Timestamp = DateTime.UtcNow,
1273 Personal = false,
1274 Note = "State-machine killed by " + LegalId
1275 };
1276
1277 await Database.Insert(Comment);
1278
1279 Killed Event = new Killed()
1280 {
1281 ArchiveOptional = Token.ArchiveOptional,
1282 ArchiveRequired = Token.ArchiveRequired,
1284 Owner = Token.Owner,
1285 OwnershipContract = Token.OwnershipContract,
1286 TokenId = TokenId,
1287 Value = 0,
1288 Currency = string.Empty,
1289 Timestamp = DateTime.UtcNow,
1290 Personal = false,
1291 LegalId = LegalId,
1292 User = User.UserName,
1293 ClientEndPoint = e.ClientEndpoint,
1294 PetitionId = e.PetitionId,
1295 Purpose = Purpose,
1296 Content = e.SignatureContent,
1297 Signature = e.Signature,
1298 ClientIdentityXml = sb.ToString()
1299 };
1300
1301 await Database.Insert(Event);
1302 }
1303
1305 e.SignatureContent, State);
1306
1307 await Callback.Raise(Sender, e2);
1308 }, null);
1309
1310 return 2;
1311 }
1312
1313 private static void Ledger_EntryAdded(object Sender, ObjectEventArgs e)
1314 {
1315 // TODO
1316 }
1317
1318 private static void Ledger_EntryUpdated(object Sender, ObjectEventArgs e)
1319 {
1320 // TODO
1321 }
1322
1323 private static void Ledger_EntryDeleted(object Sender, ObjectEventArgs e)
1324 {
1325 // TODO
1326 }
1327
1328 internal static async Task<(int, int, int, int)> DeleteExpiredMachines()
1329 {
1330 int NrMachines = 0;
1331 int NrEventHandlers = 0;
1332 int NrCurrentStates = 0;
1333 int NrSamples = 0;
1334
1335 IEnumerable<StateMachine> Deleted = await Database.FindDelete<StateMachine>(
1336 new FilterFieldLesserThan("Expires", DateTime.Today.AddDays(-1)));
1337
1338 foreach (StateMachine Machine in Deleted)
1339 {
1340 NrMachines++;
1341
1342 if (stateMachines.TryGetValue(Machine.StateMachineId, out CacheRecord Record))
1343 {
1344 stateMachines.Remove(Machine.StateMachineId);
1345
1346 if (!string.IsNullOrEmpty(Record.CurrentState?.State) &&
1347 Machine.TryGetState(Record.CurrentState.State, out State State) &&
1348 !(State.OnEvent is null))
1349 {
1350 try
1351 {
1352 Variables Variables = Record.CurrentState.GetVariables(Machine);
1354 Machine, null, Record.CurrentState, Record.Legal, Record.EDaler,
1355 Record.Profiler);
1356
1358 }
1359 catch (Exception ex)
1360 {
1361 Log.Exception(ex);
1362 }
1363 }
1364 }
1365
1366 IEnumerable<EventHandlers.EventHandler> EventHandlers = await Database.FindDelete<EventHandlers.EventHandler>(
1367 new FilterFieldEqualTo("StateMachineId", Machine.StateMachineId));
1368
1369 foreach (EventHandlers.EventHandler EventHandler in EventHandlers)
1370 {
1371 NrEventHandlers++;
1372
1375 }
1376
1377 NrCurrentStates = await Database.Delete<CurrentState>(
1378 new FilterFieldEqualTo("StateMachineId", Machine.StateMachineId));
1379
1380 NrSamples = await Database.Delete<StateMachineSample>(
1381 new FilterFieldEqualTo("StateMachineId", Machine.StateMachineId.Value));
1382 }
1383
1384 return (NrMachines, NrEventHandlers, NrCurrentStates, NrSamples);
1385 }
1386
1387 #region XMPP Interface
1388
1389 internal static void RegisterHandlers(EDalerComponent EDaler)
1390 {
1391 eDaler = EDaler;
1392
1393 EDaler.RegisterIqGetHandler("currentState", StateMachineNamespace, GetCurrentStateHandler, true);
1394 EDaler.RegisterIqGetHandler("profilingReport", StateMachineNamespace, GetProfilingReport, false);
1395 EDaler.RegisterIqGetHandler("presentReport", StateMachineNamespace, GetPresentReport, false);
1396 EDaler.RegisterIqGetHandler("historyReport", StateMachineNamespace, GetHistoryReport, false);
1397 EDaler.RegisterIqGetHandler("stateDiagram", StateMachineNamespace, GetStateDiagram, false);
1398 }
1399
1400 internal static void UnregisterHandlers(EDalerComponent EDaler)
1401 {
1402 eDaler = null;
1403
1404 EDaler.UnregisterIqGetHandler("currentState", StateMachineNamespace, GetCurrentStateHandler, true);
1405 EDaler.UnregisterIqGetHandler("profilingReport", StateMachineNamespace, GetProfilingReport, false);
1406 EDaler.UnregisterIqGetHandler("presentReport", StateMachineNamespace, GetPresentReport, false);
1407 EDaler.UnregisterIqGetHandler("historyReport", StateMachineNamespace, GetHistoryReport, false);
1408 EDaler.UnregisterIqGetHandler("stateDiagram", StateMachineNamespace, GetStateDiagram, false);
1409 }
1410
1411 #region GetCurrentStateHandler
1412
1413 private static async Task GetCurrentStateHandler(object Sender, IqEventArgs e)
1414 {
1415 string TokenId = XML.Attribute(e.Query, "tokenId");
1416 if (string.IsNullOrEmpty(TokenId))
1417 {
1418 await e.IqErrorBadRequest(e.To, "Missing Token ID.", "en");
1419 return;
1420 }
1421
1422 CurrentState CurrentState = await GetCurrentState(TokenId, e);
1423 if (CurrentState is null)
1424 return;
1425
1426 StringBuilder Xml = new StringBuilder();
1427 await Export(CurrentState, Xml);
1428
1429 await e.IqResult(Xml.ToString(), e.To);
1430 }
1431
1432 internal static async Task Export(CurrentState CurrentState, StringBuilder Xml)
1433 {
1435
1436 Xml.Append("<currentState xmlns='");
1437 Xml.Append(StateMachineNamespace);
1438 Xml.Append("' state='");
1439 Xml.Append(XML.Encode(CurrentState.State));
1440 Xml.Append("' ended='");
1442 Xml.Append("' running='");
1444 Xml.Append("' expires='");
1445 Xml.Append(XML.Encode(CurrentState.Expires));
1446 Xml.Append("'>");
1447
1448 if (!(CurrentState.VariableValues is null))
1449 {
1451 AppendVariable(Xml, Variable.Name, Variable.Value);
1452 }
1453
1454 Xml.Append("</currentState>");
1455 }
1456
1457 internal static void AppendVariable(StringBuilder Xml, string Name, object Value)
1458 {
1459 Xml.Append("<variable name='");
1460 Xml.Append(XML.Encode(Name));
1461 Xml.Append("'>");
1462
1463 if (Value is null)
1464 Xml.Append("<null/>");
1465 else if (Value is double dbl)
1466 {
1467 Xml.Append("<dbl>");
1468 Xml.Append(CommonTypes.Encode(dbl));
1469 Xml.Append("</dbl>");
1470 }
1471 else if (Value is float fl)
1472 {
1473 Xml.Append("<fl>");
1474 Xml.Append(CommonTypes.Encode(fl));
1475 Xml.Append("</fl>");
1476 }
1477 else if (Value is decimal dec)
1478 {
1479 Xml.Append("<dec>");
1480 Xml.Append(CommonTypes.Encode(dec));
1481 Xml.Append("</dec>");
1482 }
1483 else if (Value is int i32)
1484 {
1485 Xml.Append("<i32>");
1486 Xml.Append(i32.ToString());
1487 Xml.Append("</i32>");
1488 }
1489 else if (Value is long i64)
1490 {
1491 Xml.Append("<i64>");
1492 Xml.Append(i64.ToString());
1493 Xml.Append("</i64>");
1494 }
1495 else if (Value is short i16)
1496 {
1497 Xml.Append("<i16>");
1498 Xml.Append(i16.ToString());
1499 Xml.Append("</i16>");
1500 }
1501 else if (Value is sbyte i8)
1502 {
1503 Xml.Append("<i8>");
1504 Xml.Append(i8.ToString());
1505 Xml.Append("</i8>");
1506 }
1507 else if (Value is uint ui32)
1508 {
1509 Xml.Append("<ui32>");
1510 Xml.Append(ui32.ToString());
1511 Xml.Append("</ui32>");
1512 }
1513 else if (Value is ulong ui64)
1514 {
1515 Xml.Append("<ui64>");
1516 Xml.Append(ui64.ToString());
1517 Xml.Append("</ui64>");
1518 }
1519 else if (Value is ushort ui16)
1520 {
1521 Xml.Append("<ui16>");
1522 Xml.Append(ui16.ToString());
1523 Xml.Append("</ui16>");
1524 }
1525 else if (Value is byte ui8)
1526 {
1527 Xml.Append("<ui8>");
1528 Xml.Append(ui8.ToString());
1529 Xml.Append("</ui8>");
1530 }
1531 else if (Value is bool b)
1532 {
1533 Xml.Append("<b>");
1534 Xml.Append(CommonTypes.Encode(b));
1535 Xml.Append("</b>");
1536 }
1537 else if (Value is DateTime TP)
1538 {
1539 Xml.Append("<dt>");
1540 Xml.Append(XML.Encode(TP));
1541 Xml.Append("</dt>");
1542 }
1543 else if (Value is DateTimeOffset TPO)
1544 {
1545 Xml.Append("<dto>");
1546 Xml.Append(XML.Encode(TPO));
1547 Xml.Append("</dto>");
1548 }
1549 else if (Value is TimeSpan TS)
1550 {
1551 Xml.Append("<ts>");
1552 Xml.Append(XML.Encode(TS.ToString()));
1553 Xml.Append("</ts>");
1554 }
1555 else if (Value is Duration D)
1556 {
1557 Xml.Append("<d>");
1558 Xml.Append(XML.Encode(D.ToString()));
1559 Xml.Append("</d>");
1560 }
1561 else if (Value is string s)
1562 {
1563 Xml.Append("<s>");
1564 Xml.Append(XML.Encode(s));
1565 Xml.Append("</s>");
1566 }
1567 else
1568 {
1569 Xml.Append("<exp>");
1570 Xml.Append(XML.Encode(Expression.ToExpressionString(Value)));
1571 Xml.Append("</exp>");
1572 }
1573
1574 Xml.Append("</variable>");
1575 }
1576
1577 private static async Task<CacheRecord> GetCacheRecord(string TokenId, IqEventArgs e)
1578 {
1579 XmppAddress Addr = new XmppAddress(TokenId);
1580 if (!Addr.IsBareJID || !Guid.TryParse(Addr.Account, out _))
1581 {
1582 e?.IqErrorBadRequest(e.To, "Invalid Token ID.", "en");
1583 return null;
1584 }
1585
1586 if (!eDaler.IsComponentDomain(Addr.Domain, true))
1587 {
1588 e?.IqErrorBadRequest(e.To, "Token not hosted by this neuron.", "en");
1589 return null;
1590 }
1591
1592 Token Token = await NeuroFeaturesProcessor.GetToken(TokenId, true);
1593 if (Token is null)
1594 {
1595 e?.IqErrorItemNotFound(e.To, "Token not found.", "en");
1596 return null;
1597 }
1598
1599 if (!(e is null) && !await NeuroFeaturesProcessor.IsAuthorizedAccess(Token, e))
1600 return null;
1601
1602 CacheRecord Record = await GetStateMachine(Token.MachineId, false);
1603 if (Record is null)
1604 {
1605 e?.IqErrorItemNotFound(e.To, "State-Machine not found.", "en");
1606 return null;
1607 }
1608
1609 return Record;
1610 }
1611
1612 private static async Task<CurrentState> GetCurrentState(string TokenId, IqEventArgs e)
1613 {
1614 CacheRecord Record = await GetCacheRecord(TokenId, e);
1615 if (Record is null)
1616 return null;
1617
1618 return Record.CurrentState;
1619 }
1620
1621 #endregion
1622
1623 #region GetProfilingReport
1624
1625 internal static async Task<string> GetProfilingReportAsync(string TokenId, ReportFormat Format)
1626 {
1627 CacheRecord Record = await GetCacheRecord(TokenId, null);
1628 if (Record?.Profiler is null)
1629 return string.Empty;
1630
1631 return await GetProfilingReport(Record.Machine.StateMachineId.Value,
1632 Record.Profiler, Format);
1633 }
1634
1635 private static async Task GetProfilingReport(object Sender, IqEventArgs e)
1636 {
1637 string TokenId = XML.Attribute(e.Query, "tokenId");
1638 if (string.IsNullOrEmpty(TokenId))
1639 {
1640 await e.IqErrorBadRequest(e.To, "Missing Token ID.", "en");
1641 return;
1642 }
1643
1644 CacheRecord Record = await GetCacheRecord(TokenId, e);
1645 if (Record is null)
1646 return;
1647
1648 if (Record?.Profiler is null)
1649 {
1650 await e.IqErrorServiceUnavailable(e.To, "Profiler not activated on the state-machine.", "en");
1651 return;
1652 }
1653
1654 ReportFormat ReportFormat = XML.Attribute(e.Query, "format", ReportFormat.Markdown);
1655 StringBuilder Xml = new StringBuilder();
1656
1657 Xml.Append("<report xmlns='");
1658 Xml.Append(StateMachineNamespace);
1659 Xml.Append("'>");
1660
1661 Xml.Append(XML.Encode(await GetProfilingReport(
1662 Record.Machine.StateMachineId.Value, Record.Profiler, ReportFormat)));
1663
1664 Xml.Append("</report>");
1665
1666 await e.IqResult(Xml.ToString(), e.To);
1667 }
1668
1669 private static async Task<string> GetProfilingReport(string StateMachineId, Profiler Profiler,
1670 ReportFormat Format)
1671 {
1672 using Semaphore Semaphore = await Semaphores.BeginRead("machine:" + StateMachineId);
1673 StringBuilder Markdown = new StringBuilder();
1674
1675 Markdown.AppendLine("Timing Diagram");
1676 Markdown.AppendLine("==================");
1677 Markdown.AppendLine();
1678 Markdown.AppendLine("```uml");
1679 Profiler.ExportPlantUml(Markdown, TimeUnit.DynamicPerProfiling);
1680 Markdown.AppendLine("```");
1681
1682 int i, c = Profiler.NoteCount;
1683
1684 for (i = 1; i <= c; i++)
1685 {
1686 if (Profiler.TryGetNote(i, out object Note))
1687 {
1688 Markdown.AppendLine();
1689 Markdown.Append("Note ");
1690 Markdown.AppendLine(i.ToString());
1691 Markdown.AppendLine("-----------------");
1692 Markdown.AppendLine();
1693
1694 if (Note is string s)
1695 {
1696 if (s.StartsWith("@startjson") && s.EndsWith("@endjson"))
1697 {
1698 Markdown.AppendLine("```uml");
1699 Markdown.AppendLine(s);
1700 Markdown.AppendLine("```");
1701 }
1702 else
1703 Markdown.AppendLine(MarkdownDocument.Encode(s));
1704 }
1705 else if (Note is ScriptRuntimeException ScriptError)
1706 {
1707 Markdown.AppendLine(MarkdownDocument.Encode(ScriptError.Message));
1708 Markdown.AppendLine();
1709 Markdown.AppendLine("```");
1710 Markdown.AppendLine(ScriptError.Node?.SubExpression ?? ScriptError.StackTrace);
1711 Markdown.AppendLine("```");
1712 }
1713 else if (Note is StateMachineErrorException MachineError)
1714 {
1715 Markdown.AppendLine(MarkdownDocument.Encode(MachineError.Message));
1716 }
1717 else if (Note is Exception ex)
1718 {
1719 Markdown.AppendLine(MarkdownDocument.Encode(ex.Message));
1720 Markdown.AppendLine();
1721 Markdown.AppendLine("```");
1722 Markdown.AppendLine(ex.StackTrace);
1723 Markdown.AppendLine("```");
1724 }
1725 else
1726 {
1727 Markdown.AppendLine("```uml");
1728 Markdown.AppendLine("@startjson");
1729 Markdown.AppendLine(JSON.Encode(Note, true));
1730 Markdown.AppendLine("@endjson");
1731 Markdown.AppendLine("```");
1732 }
1733 }
1734 }
1735
1736 return await NeuroFeaturesProcessor.FormatReport(Markdown.ToString(), Format, HttpServer.CreateSessionVariables());
1737 }
1738
1739 #endregion
1740
1741 #region GetPresentReport
1742
1743 internal static async Task<string> GetPresentReportAsync(Token Token, ReportFormat Format)
1744 {
1745 CacheRecord Record = await GetStateMachine(Token, false);
1746 if (Record is null)
1747 return string.Empty;
1748
1749 return await GetPresentReport(Record, Format);
1750 }
1751
1752 private static async Task GetPresentReport(object Sender, IqEventArgs e)
1753 {
1754 string TokenId = XML.Attribute(e.Query, "tokenId");
1755 if (string.IsNullOrEmpty(TokenId))
1756 {
1757 await e.IqErrorBadRequest(e.To, "Missing Token ID.", "en");
1758 return;
1759 }
1760
1761 CacheRecord Record = await GetCacheRecord(TokenId, e);
1762 if (Record is null)
1763 return;
1764
1765 ReportFormat ReportFormat = XML.Attribute(e.Query, "format", ReportFormat.Markdown);
1766 StringBuilder Xml = new StringBuilder();
1767
1768 Xml.Append("<report xmlns='");
1769 Xml.Append(StateMachineNamespace);
1770 Xml.Append("'>");
1771 Xml.Append(XML.Encode(await GetPresentReport(Record, ReportFormat)));
1772 Xml.Append("</report>");
1773
1774 await e.IqResult(Xml.ToString(), e.To);
1775 }
1776
1777 private static async Task<string> GetPresentReport(CacheRecord Record, ReportFormat Format)
1778 {
1779 using Semaphore Semaphore = await Semaphores.BeginRead("machine:" + Record.Machine.StateMachineId.Value);
1780 string Markdown = null;
1781
1782 foreach (IStateMachineNode Node in Record.Machine.Root.ChildNodes)
1783 {
1784 if (Node is ReportPresent Report)
1785 {
1786 Markdown = Report.Markdown;
1787 break;
1788 }
1789 }
1790
1791 if (string.IsNullOrEmpty(Markdown))
1792 return string.Empty;
1793
1794 Variables Variables = Record.CurrentState.GetVariables(Record.Machine);
1795
1796 return await NeuroFeaturesProcessor.FormatReport(Markdown, Format, Variables);
1797 }
1798
1799 #endregion
1800
1801 #region GetHistoryReport
1802
1803 internal static async Task<string> GetHistoryReportAsync(Token Token, ReportFormat Format)
1804 {
1805 CacheRecord Record = await GetStateMachine(Token, false);
1806 if (Record is null)
1807 return string.Empty;
1808
1809 return await GetHistoryReport(Record, Format);
1810 }
1811
1812 private static async Task GetHistoryReport(object Sender, IqEventArgs e)
1813 {
1814 string TokenId = XML.Attribute(e.Query, "tokenId");
1815 if (string.IsNullOrEmpty(TokenId))
1816 {
1817 await e.IqErrorBadRequest(e.To, "Missing Token ID.", "en");
1818 return;
1819 }
1820
1821 CacheRecord Record = await GetCacheRecord(TokenId, e);
1822 if (Record is null)
1823 return;
1824
1825 ReportFormat ReportFormat = XML.Attribute(e.Query, "format", ReportFormat.Markdown);
1826 StringBuilder Xml = new StringBuilder();
1827
1828 Xml.Append("<report xmlns='");
1829 Xml.Append(StateMachineNamespace);
1830 Xml.Append("'>");
1831 Xml.Append(XML.Encode(await GetHistoryReport(Record, ReportFormat)));
1832 Xml.Append("</report>");
1833
1834 await e.IqResult(Xml.ToString(), e.To);
1835 }
1836
1837 private static async Task<string> GetHistoryReport(CacheRecord Record, ReportFormat Format)
1838 {
1839 using Semaphore Semaphore = await Semaphores.BeginRead("machine:" + Record.Machine.StateMachineId.Value);
1840 string Markdown = null;
1841
1842 foreach (IStateMachineNode Node in Record.Machine.Root.ChildNodes)
1843 {
1844 if (Node is ReportHistory Report)
1845 {
1846 Markdown = Report.Markdown;
1847 break;
1848 }
1849 }
1850
1851 if (string.IsNullOrEmpty(Markdown))
1852 return string.Empty;
1853
1854 Variables Variables = await GetHistoryVariables(Record);
1855
1856 return await NeuroFeaturesProcessor.FormatReport(Markdown, Format, Variables);
1857 }
1858
1859
1865 public static async Task<Variables> GetHistoryVariables(string MachineId)
1866 {
1867 CacheRecord Record = await GetStateMachine(MachineId, false);
1868 return await GetHistoryVariables(Record);
1869 }
1870
1871 private static async Task<Variables> GetHistoryVariables(CacheRecord Record)
1872 {
1873 Dictionary<string, SortedDictionary<DateTime, StateMachineSample>> ByVariableAndTime = new Dictionary<string, SortedDictionary<DateTime, StateMachineSample>>();
1874 SortedDictionary<DateTime, StateMachineSample> ByTime = null;
1875 IEnumerable<StateMachineSample> Samples = await Database.Find<StateMachineSample>(
1876 new FilterFieldEqualTo("StateMachineId", Record.Machine.StateMachineId.Value),
1877 "Variable", "Timestamp");
1878 string LastVariable = null;
1879 Variables Variables = Record.CurrentState.GetVariables(Record.Machine);
1880 DateTime Now = DateTime.UtcNow;
1881
1882 foreach (StateMachineSample Sample in Samples)
1883 {
1884 if (ByTime is null || Sample.Variable != LastVariable)
1885 {
1886 LastVariable = Sample.Variable;
1887
1888 if (!ByVariableAndTime.TryGetValue(LastVariable, out ByTime))
1889 {
1890 ByTime = new SortedDictionary<DateTime, StateMachineSample>(timestampDescending);
1891 ByVariableAndTime[LastVariable] = ByTime;
1892 }
1893 }
1894
1895 ByTime[Sample.Timestamp] = Sample;
1896 }
1897
1898 foreach (KeyValuePair<string, SortedDictionary<DateTime, StateMachineSample>> P in ByVariableAndTime)
1899 {
1900 if (Variables.TryGetVariable(P.Key, out Script.Variable v))
1901 {
1902 P.Value[Now] = new StateMachineSample()
1903 {
1904 StateMachineId = Record.Machine.StateMachineId,
1905 Variable = P.Key,
1906 Value = v.ValueObject,
1907 Timestamp = Now,
1908 Expires = Record.Machine.Expires,
1909 ArchiveOptional = Record.Machine.ArchiveOptional,
1910 ArchiveRequired = Record.Machine.ArchiveRequired
1911 };
1912 }
1913
1914 StateMachineSample[] History = new StateMachineSample[P.Value.Count];
1915 P.Value.Values.CopyTo(History, 0);
1916 Variables[P.Key] = History;
1917 }
1918
1919 return Variables;
1920 }
1921
1922 private class TimestampDescending : IComparer<DateTime>
1923 {
1924 public int Compare(DateTime x, DateTime y)
1925 {
1926 return y.CompareTo(x);
1927 }
1928 }
1929
1930 private static readonly TimestampDescending timestampDescending = new TimestampDescending();
1931
1932 #endregion
1933
1934 #region GetStateDiagram
1935
1936 internal static async Task<string> GetStateDiagramAsync(Token Token, ReportFormat Format)
1937 {
1938 CacheRecord Record = await GetStateMachine(Token, false);
1939 if (Record is null)
1940 return string.Empty;
1941
1942 return await GetStateDiagram(Record, Format);
1943 }
1944
1945 private static async Task GetStateDiagram(object Sender, IqEventArgs e)
1946 {
1947 string TokenId = XML.Attribute(e.Query, "tokenId");
1948 if (string.IsNullOrEmpty(TokenId))
1949 {
1950 await e.IqErrorBadRequest(e.To, "Missing Token ID.", "en");
1951 return;
1952 }
1953
1954 CacheRecord Record = await GetCacheRecord(TokenId, e);
1955 if (Record is null)
1956 return;
1957
1958 ReportFormat ReportFormat = XML.Attribute(e.Query, "format", ReportFormat.Markdown);
1959 StringBuilder Xml = new StringBuilder();
1960
1961 Xml.Append("<report xmlns='");
1962 Xml.Append(StateMachineNamespace);
1963 Xml.Append("'>");
1964 Xml.Append(XML.Encode(await GetStateDiagram(Record, ReportFormat)));
1965 Xml.Append("</report>");
1966
1967 await e.IqResult(Xml.ToString(), e.To);
1968 }
1969
1970 private static async Task<string> GetStateDiagram(CacheRecord Record, ReportFormat Format)
1971 {
1972 using Semaphore Semaphore = await Semaphores.BeginRead("machine:" + Record.Machine.StateMachineId.Value);
1973 StringBuilder Markdown = new StringBuilder();
1974 StateMachine Machine = Record.Machine;
1975 Dictionary<string, string> States = new Dictionary<string, string>();
1976 LinkedList<State> StateNodes = new LinkedList<State>();
1977 string s;
1978 int AIndex = 0;
1979
1980 Markdown.AppendLine("```uml");
1981 Markdown.AppendLine("@startuml");
1982 Markdown.AppendLine();
1983 Markdown.AppendLine("skinparam state {");
1984 Markdown.AppendLine("\tFontSize 12");
1985 Markdown.AppendLine("\tFontStyle Italic");
1986 Markdown.AppendLine("\tAttributeFontSize 18");
1987 Markdown.AppendLine("\tBackgroundColor<<Action>> LightSalmon");
1988 Markdown.AppendLine("\tBackgroundColor<<Calc>> LightBlue");
1989 Markdown.AppendLine("\tBackgroundColor<<Current>> LightGreen");
1990 Markdown.AppendLine("\tFontStyle<<Current>> Bold, Italic");
1991 Markdown.AppendLine("\tAttributeFontStyle<<Current>> Bold");
1992 Markdown.AppendLine("\tBorderColor<<Current>> Black");
1993 Markdown.AppendLine("}");
1994 Markdown.AppendLine();
1995 Markdown.AppendLine("skinparam note {");
1996 Markdown.AppendLine("\tBackgroundColor<<Warning>> Salmon");
1997 Markdown.AppendLine("}");
1998 Markdown.AppendLine();
1999
2000 Markdown.AppendLine("state \"Initialize\" as Init");
2001 Markdown.AppendLine("[*] --> Init");
2002
2003 Markdown.Append("Init --> ");
2004 Markdown.AppendLine(GetStateLabel(States, Machine.Root.StartState));
2005
2006 foreach (IStateMachineNode Node in Machine.Root.ChildNodes)
2007 {
2008 if (Node is Model.Variable Variable)
2009 {
2010 Markdown.Append("Init : ");
2011 Markdown.Append(Variable.Id);
2012
2013 if (Record.CurrentState.TryGetVariable(Variable.Id, out CurrentStateVariable StateVariable) &&
2014 !string.IsNullOrEmpty(StateVariable.InitExpression))
2015 {
2016 Markdown.Append(":=");
2017 Markdown.Append(EncodeLabel(StateVariable.InitExpression));
2018 }
2019
2020 Markdown.AppendLine();
2021 }
2022 else if (Node is State State)
2023 {
2024 StateNodes.AddLast(State);
2025
2026 Markdown.AppendLine();
2027 Markdown.Append("state \"State\" as ");
2028 Markdown.Append(GetStateLabel(States, State.Id));
2029
2030 if (State.Id == Record.CurrentState.State)
2031 Markdown.Append(" <<Current>>");
2032
2033 Markdown.Append(" : ");
2034 Markdown.AppendLine(EncodeLabel(State.Id));
2035 }
2036 }
2037
2038 foreach (State State in StateNodes)
2039 {
2040 s = GetStateLabel(States, State.Id);
2041
2042 if (State.OnEvent is null)
2043 {
2044 Markdown.Append("note right of ");
2045 Markdown.Append(s);
2046 Markdown.AppendLine(" <<Warning>> : No events defined.");
2047 continue;
2048 }
2049
2050 foreach (OnEvent Event in State.OnEvent)
2051 GenerateEventStateDiagram(Event, Markdown, State, ref AIndex, s, States, Machine);
2052
2053 Markdown.AppendLine();
2054 }
2055
2056 Markdown.AppendLine("@enduml");
2057 Markdown.AppendLine("```");
2058
2059 return await NeuroFeaturesProcessor.FormatReport(Markdown.ToString(), Format, HttpServer.CreateSessionVariables());
2060 }
2061
2062 private static void GenerateEventStateDiagram(OnEvent Event, StringBuilder Markdown,
2063 State State, ref int AIndex, string s, Dictionary<string, string> States,
2064 StateMachine Machine)
2065 {
2066 string s2;
2067 string s3 = Event.Event.Label;
2068
2069 if (!(State.OnLeave is null))
2070 {
2071 foreach (OnLeave OnLeave in State.OnLeave)
2072 {
2073 AIndex++;
2074 s2 = "A" + AIndex.ToString();
2075
2076 Markdown.Append("state \"OnLeave Action\" as ");
2077 Markdown.Append(s2);
2078 Markdown.Append(" <<Action>> : ");
2079 Markdown.AppendLine(EncodeLabel(OnLeave.ActionReferenceDefinition));
2080
2081 Markdown.Append(s);
2082 Markdown.Append(" --> ");
2083 Markdown.Append(s2);
2084
2085 if (!string.IsNullOrEmpty(s3))
2086 {
2087 Markdown.Append(" : ");
2088 Markdown.Append(EncodeLabel(s3));
2089 s3 = string.Empty;
2090 }
2091
2092 Markdown.AppendLine();
2093 s = s2;
2094 }
2095 }
2096
2097 if (Event.HasActionReference)
2098 {
2099 AIndex++;
2100 s2 = "A" + AIndex.ToString();
2101
2102 Markdown.Append("state \"Event Action\" as ");
2103 Markdown.Append(s2);
2104 Markdown.Append(" <<Action>> : ");
2105 Markdown.AppendLine(EncodeLabel(Event.ActionReferenceDefinition));
2106
2107 Markdown.Append(s);
2108 Markdown.Append(" --> ");
2109 Markdown.Append(s2);
2110
2111 if (!string.IsNullOrEmpty(s3))
2112 {
2113 Markdown.Append(" : ");
2114 Markdown.Append(EncodeLabel(s3));
2115 s3 = string.Empty;
2116 }
2117
2118 Markdown.AppendLine();
2119 s = s2;
2120
2121 if (Event.HasFailureState)
2122 s3 = "OK";
2123 }
2124 else if (Event.HasFailureState)
2125 {
2126 if (string.IsNullOrEmpty(s3))
2127 s3 = "OK";
2128 else
2129 s3 += " and OK";
2130 }
2131
2132 ActionReference[] Actions;
2133 bool Termination;
2134
2135 if (Event.HasNewState && Machine.TryGetState(Event.NewStateDefinition, out State NewState))
2136 {
2137 Actions = NewState.OnEnter;
2138 Termination = NewState.IsDone(Machine);
2139 }
2140 else
2141 {
2142 Actions = null;
2143 Termination = false;
2144 }
2145
2146 GenerateOnEnterBranch(Event, Markdown, State, ref AIndex, s, States,
2147 Actions, s3, Event.NewStateDefinition, Termination);
2148
2149 if (Event.HasFailureState)
2150 {
2151 if (Machine.TryGetState(Event.FailureStateDefinition, out State FailureState))
2152 {
2153 Actions = FailureState.OnEnter;
2154 Termination = FailureState.IsDone(Machine);
2155 }
2156 else
2157 {
2158 Actions = null;
2159 Termination = false;
2160 }
2161
2162 GenerateOnEnterBranch(Event, Markdown, State, ref AIndex, s, States,
2163 Actions, "Error", Event.FailureStateDefinition, Termination);
2164 }
2165 }
2166
2167 private static void GenerateOnEnterBranch(OnEvent Event, StringBuilder Markdown,
2168 State State, ref int AIndex, string s, Dictionary<string, string> States,
2169 ActionReference[] Actions, string s3, string NewState, bool Termination)
2170 {
2171 string s2;
2172
2173 if (!(Actions is null) && !Termination)
2174 {
2175 foreach (ActionReference Action in Actions)
2176 {
2177 AIndex++;
2178 s2 = "A" + AIndex.ToString();
2179
2180 Markdown.Append("state \"OnEnter Action\" as ");
2181 Markdown.Append(s2);
2182 Markdown.Append(" <<Action>> : ");
2183 Markdown.AppendLine(EncodeLabel(Action.ActionReferenceDefinition));
2184
2185 Markdown.Append(s);
2186 Markdown.Append(" --> ");
2187 Markdown.Append(s2);
2188
2189 if (!string.IsNullOrEmpty(s3))
2190 {
2191 Markdown.Append(" : ");
2192 Markdown.Append(EncodeLabel(s3));
2193 s3 = string.Empty;
2194 }
2195
2196 Markdown.AppendLine();
2197 s = s2;
2198 }
2199 }
2200
2201 if (Termination)
2202 s2 = "[*]";
2203 else
2204 {
2205 if (string.IsNullOrEmpty(NewState))
2206 NewState = State.Id;
2207
2208 bool VarState = !States.ContainsKey(NewState);
2209
2210 s2 = GetStateLabel(States, NewState);
2211
2212 if (VarState)
2213 {
2214 Markdown.Append("state \"Calc\" as ");
2215 Markdown.Append(s2);
2216 Markdown.Append(" <<Calc>> : ");
2217 Markdown.AppendLine(EncodeLabel(Event.NewStateDefinition));
2218
2219 Markdown.Append("note right of ");
2220 Markdown.Append(s2);
2221 Markdown.AppendLine(" <<Warning>> : Variable state, through script.");
2222 }
2223 }
2224
2225 Markdown.Append(s);
2226 Markdown.Append(" --> ");
2227 Markdown.Append(s2);
2228
2229 if (!string.IsNullOrEmpty(s3))
2230 {
2231 Markdown.Append(" : ");
2232 Markdown.Append(EncodeLabel(s3));
2233 }
2234
2235 Markdown.AppendLine();
2236 }
2237
2238
2239 private static string GetStateLabel(Dictionary<string, string> States, string StateId)
2240 {
2241 if (!States.TryGetValue(StateId, out string Id))
2242 {
2243 int Index = States.Count + 1;
2244 States[StateId] = Id = "S" + Index.ToString();
2245 }
2246
2247 return Id;
2248 }
2249
2250 private static string EncodeLabel(string s)
2251 {
2252 return s.
2253 Replace("\\", "\\\\").
2254 Replace("\"", "'").
2255 Replace("\a", "\\a").
2256 Replace("\b", "\\b").
2257 Replace("\f", "\\f").
2258 Replace("\n", "\\n").
2259 Replace("\r", "\\r").
2260 Replace("\t", "\\t").
2261 Replace("\v", "\\v");
2262 }
2263
2264 #endregion
2265
2266 #endregion
2267
2268 }
2269}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Definition: CommonTypes.cs:596
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:537
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:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
static XmlDocument ParseXml(string Xml)
Parses an XML Document from its string representation.
Definition: XML.cs:713
Class representing an event.
Definition: Event.cs:11
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:39
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
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:1657
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:692
Implements an HTTP server.
Definition: HttpServer.cs:41
static SessionVariables CreateSessionVariables()
Creates a new collection of variables, that contains access to the global set of variables.
Definition: HttpServer.cs:2130
bool IsComponentDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the component domain, or optionally, an alternative component domain.
Definition: Component.cs:124
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:206
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:220
Task IqErrorBadRequest(XmppAddress From, string ErrorText, string Language)
Returns a bad-request error.
Definition: IqEventArgs.cs:164
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....
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
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:21
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:1442
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:1291
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
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:17
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
Static class managing persistent counters.
static Task< bool > AddHash(byte[] Hash)
Persists a hash, using the default (empty) realm.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
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:1454
static Type[] GetTypesImplementingInterface(string InterfaceFullName)
Gets all types implementing a given interface.
Definition: Types.cs:85
Class that keeps track of events and timing.
Definition: Profiler.cs:68
bool TryGetNote(int Index, out object Note)
Tries to get a note from the profile.
Definition: Profiler.cs:749
Profiler()
Class that keeps track of events and timing.
Definition: Profiler.cs:83
string ExportPlantUml(TimeUnit TimeUnit)
Exports events to PlantUML.
Definition: Profiler.cs:530
int NoteCount
Number of notes added.
Definition: Profiler.cs:733
void Start()
Starts measuring time.
Definition: Profiler.cs:217
Represents a named semaphore, i.e. an object, identified by a name, that allows single concurrent wri...
Definition: Semaphore.cs:19
async Task DisposeAsync()
Disposes of the object, asynchronously.
Definition: Semaphore.cs:199
Static class of application-wide semaphores that can be used to order access to editable objects.
Definition: Semaphores.cs:17
static async Task< Semaphore > BeginRead(string Key)
Waits until the semaphore identified by Key is ready for reading. Each call to BeginRead must be fol...
Definition: Semaphores.cs:54
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:91
Class managing a script expression.
Definition: Expression.cs:41
static string ToExpressionString(object Value)
Converts an object to a string, that can be parsed as part of an expression.
Definition: Expression.cs:5052
Contains information about a variable.
Definition: Variable.cs:10
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:56
Contains methods for simple hash calculations.
Definition: Hashes.cs:57
static byte[] ComputeSHA256Hash(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
Definition: Hashes.cs:469
byte[] ComputeVariable(byte[] N)
Computes the SPONGE function, as defined in section 4 of NIST FIPS 202.
Definition: Keccak1600.cs:408
Implements the SHA3-256 hash function, as defined in section 6.1 in the NIST FIPS 202: https://nvlpub...
Definition: SHA3_256.cs:9
Corresponds to a privilege in the system.
Definition: Privilege.cs:16
Corresponds to a role in the system.
Definition: Role.cs:15
Maintains the collection of all roles in the system.
Definition: Roles.cs:14
Corresponds to a user in the system.
Definition: User.cs:24
string UserName
User Name
Definition: User.cs:60
bool HasPrivilege(string Privilege)
If the user has a given privilege.
Definition: User.cs:187
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.
Event raised when a state-machine has been killed by an operator.
Definition: Killed.cs:11
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.
bool HasStateMachine
If the token has an associated state-machine.
Definition: Token.cs:1345
Duration? ArchiveOptional
Duration after which token expires, and the required archiving time, the token can optionally be arch...
Definition: Token.cs:422
string Definition
M2M definition of token, in XML, from the original creation contract.
Definition: Token.cs:262
CaseInsensitiveString OwnershipContract
ID of contract that details the claims of the current owner
Definition: Token.cs:316
string DefinitionNamespace
Namespace of M2M definition of token.
Definition: Token.cs:271
CaseInsensitiveString Owner
Current owner of token
Definition: Token.cs:207
CaseInsensitiveString TrustProviderJid
JID of Trust Provider, asserting claims in the token.
Definition: Token.cs:234
CaseInsensitiveString CreationContract
ID of contract that details the creation of the token.
Definition: Token.cs:298
XmlDocument DefinitionParsed
Parsed definition
Definition: Token.cs:280
Duration? ArchiveRequired
Duration after which token expires, the token is required to be archived.
Definition: Token.cs:412
CaseInsensitiveString MachineId
State Machine ID, if any
Definition: Token.cs:163
CaseInsensitiveString TrustProvider
Trust Provider, asserting claims in the token.
Definition: Token.cs:225
DateTime Expires
Expiry date of token.
Definition: Token.cs:402
CaseInsensitiveString TokenId
Token ID
Definition: Token.cs:145
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
CaseInsensitiveString StateMachineId
ID of State-Machine.
Definition: CurrentState.cs:41
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 someone signs a contract where the owner of the associated token is part.
Event raised when a contract where the owner of the associated token is part, has become signed.
Event raised when an associated token gets destroyed.
Definition: OnDestroyed.cs:7
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 an 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:19
Action executed when entering a state.
Definition: OnLeave.cs:9
State()
Represents an action definition.
Definition: State.cs:22
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:43
StateMachineRoot Root
Root of State-Machine model.
Definition: StateMachine.cs:67
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:98
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:62
async Task Start(EvaluationArguments Arguments)
Starts the processing of the state-machine.
string ObjectId
Object ID of state machine.
Definition: StateMachine.cs:52
CaseInsensitiveString StateMachineId
ID of State Machine.
Definition: StateMachine.cs:57
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.
Duration? ArchiveOptional
Duration after which token expires, and the required archiving time, the token can optionally be arch...
DateTime Expires
When state-machine expires
Definition: StateMachine.cs:72
static async Task CheckConditionalEvents(EvaluationArguments Arguments)
Checks conditional events.
static async Task UnregisterEventHandlers(OnEvent[] Events, EvaluationArguments Arguments)
Unregisters event handlers for the current state.
const string StateMachineDefinition
Local name of state-machine definition
static Task< StateMachine > Parse(Token Token)
Parses the XML representation of a State Machine.
static Task< StateMachine > Parse(string NormalizedXml, XmlDocument Xml, Token Token)
Parses the XML representation of a State Machine.
static async Task< Variables > GetHistoryVariables(string MachineId)
Gets history variables for a state-machine.
static async Task< StateMachine > Parse(string Xml, Token Token)
Parses the XML representation of a State Machine.
const string StateMachineNamespace
https://paiwise.tagroot.io/Schema/StateMachines.xsd
static async Task< StateMachine > Parse(string NormalizedXml, 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.
Definition: ImplTypes.g.cs:58
TimeUnit
Options for presenting time in reports.
Definition: Profiler.cs:17
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:14