Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
StateMachine.cs
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Threading.Tasks;
5using System.Xml;
6using Waher.Content;
8using Waher.Events;
14using Waher.Script;
23using Waher.Things;
24
26{
30 [CollectionName("StateMachines")]
31 [TypeName(TypeNameSerialization.FullName)]
32 [ArchivingTime(nameof(ArchiveDays))]
33 [Index("StateMachineId")]
34 [Index("Expires", "StateMachineId")]
35 [ObsoleteMethod(nameof(SetObsoleteProperties))]
37 {
38 private Dictionary<string, Model.Actions.Action> actions;
39 private Dictionary<string, Model.Events.Event> events;
40 private Dictionary<string, State> states;
41
45 [ObjectId]
46 public string ObjectId { get; set; }
47
52
57
61 public StateMachineRoot Root { get; set; }
62
66 public DateTime Expires { get; set; }
67
71 public string XmlDefinition { get; set; }
72
77
82
87
91 [DefaultValueNull]
92 public Duration? ArchiveRequired { get; set; }
93
97 [DefaultValueNull]
98 public Duration? ArchiveOptional { get; set; }
99
104
108 public void IndexElements()
109 {
110 this.actions = new Dictionary<string, Model.Actions.Action>();
111 this.events = new Dictionary<string, Model.Events.Event>();
112 this.states = new Dictionary<string, State>();
113
114 this.Root?.ForEach((Node, State) =>
115 {
116 Node.IndexElement(this);
117 return true;
118 }, null);
119 }
120
125 public void Index(Model.Actions.Action Action)
126 {
127 if (this.actions.ContainsKey(Action.Id))
128 throw new Exception("Duplicate Action ID: " + Action.Id);
129
130 this.actions[Action.Id] = Action;
131 }
132
137 public void Index(Model.Events.Event Event)
138 {
139 if (this.events.ContainsKey(Event.Id))
140 throw new Exception("Duplicate Event ID: " + Event.Id);
141
142 this.events[Event.Id] = Event;
143 }
144
149 public void Index(State State)
150 {
151 if (this.states.ContainsKey(State.Id))
152 throw new Exception("Duplicate State ID: " + State.Id);
153
154 this.states[State.Id] = State;
155 }
156
162 {
163 this.IndexElements();
164
165 this.Root?.ForEach((Node, State) =>
166 {
167 Node.CheckReferences(this, Token);
168 return true;
169 }, null);
170 }
171
178 public bool TryGetAction(string Id, out Model.Actions.Action Action)
179 {
180 if (Id is null)
181 {
182 Action = null;
183 return false;
184 }
185 else
186 return this.actions.TryGetValue(Id, out Action);
187 }
188
195 public bool TryGetEvent(string Id, out Model.Events.Event Event)
196 {
197 if (Id is null)
198 {
199 Event = null;
200 return false;
201 }
202 else
203 return this.events.TryGetValue(Id, out Event);
204 }
205
212 public bool TryGetState(string Id, out State State)
213 {
214 if (Id is null)
215 {
216 State = null;
217 return false;
218 }
219 else
220 return this.states.TryGetValue(Id, out State);
221 }
222
232 {
233 List<CurrentStateVariable> VariableValues = new List<CurrentStateVariable>();
234 CurrentState CurrentState = await StateMachineProcessor.GetOrCreateCurrentState(this);
237 List<StateMachineSample> Samples = null;
238
239 if (!(this.Root.ChildNodes is null))
240 {
241 foreach (IStateMachineNode Node in this.Root.ChildNodes)
242 {
243 if (Node is Model.Variable Variable)
244 {
245 try
246 {
247 object Value = await Variable.Evaluate(Arguments);
248
249 Variables[Variable.Id] = Value;
250 VariableValues.Add(new CurrentStateVariable(Variable.Id, Value));
251
252 if (Samples is null)
253 Samples = new List<StateMachineSample>();
254
255 Samples.Add(new StateMachineSample()
256 {
257 Variable = Variable.Id,
258 Timestamp = DateTime.UtcNow,
259 Value = Value,
261 Expires = this.Expires,
263 ArchiveRequired = this.ArchiveRequired
264 });
265 }
266 catch (Exception ex)
267 {
268 Log.Critical("Unable to evaluate inital value for variable " +
269 Variable.Id + " for state-machine. Error reported: " + ex.Message, this.ObjectId);
270 }
271 }
272 }
273 }
274
275 CurrentState.VariableValues = VariableValues.ToArray();
276
278
279 if (!(Samples is null))
280 await Database.Insert(Samples.ToArray());
281
282 return (CurrentState, Arguments);
283 }
284
289 public async Task Start(EvaluationArguments Arguments)
290 {
291 await GoToState(this.Root.StartState, Arguments);
292 }
293
299 public static async Task GoToState(string StateId, EvaluationArguments Arguments)
300 {
301 DateTime Start = DateTime.Now;
302
303 try
304 {
305 do
306 {
307 bool NewState = StateId != Arguments.CurrentState.State;
308
309 if (!string.IsNullOrEmpty(Arguments.CurrentState.State) &&
310 Arguments.Machine.TryGetState(Arguments.CurrentState.State, out State State))
311 {
312 if (NewState)
313 await UnregisterEventHandlers(State.OnEvent, Arguments);
314
315 await ExecuteLog(State.OnLeave, Arguments);
316 }
317
318 State = null;
319
320 if (!string.IsNullOrEmpty(StateId) && !Arguments.Machine.TryGetState(StateId, out State))
321 throw new Exception("State not found:" + StateId);
322
323 if (NewState)
324 {
325 Arguments.SetState(StateId);
326
328 {
330 Timestamp = DateTime.UtcNow,
331 Value = StateId,
333 Expires = Arguments.Machine.Expires,
335 ArchiveRequired = Arguments.Machine.ArchiveRequired
336 });
337 }
338
339 StateId = null;
340
341 if (!(State is null))
342 {
343 IEnumerable<OnEvent> EventsTriggered = null;
344
345 try
346 {
347 await ExecuteLog(State.OnEnter, Arguments);
348 }
349 catch (Exception ex)
350 {
351 Log.Exception(ex, Arguments.Token?.TokenId ?? Arguments.Machine.StateMachineId);
352 }
353
354 if (NewState)
355 EventsTriggered = await RegisterEventHandlers(State.OnEvent, Arguments);
356
357 if (!(EventsTriggered is null))
358 {
359 foreach (OnEvent EventTriggered in EventsTriggered)
360 {
361 try
362 {
363 await EventTriggered.ExecuteLog(Arguments);
364 StateId = await EventTriggered.GetNewState(Arguments);
365 }
366 catch (Exception ex)
367 {
368 StateId = await EventTriggered.GetFailureState(Arguments);
369
370 if (string.IsNullOrEmpty(StateId))
371 Log.Error(ex, Arguments.Machine.ObjectId);
372 }
373
374 if (!string.IsNullOrEmpty(StateId))
375 break;
376 }
377
378 double ElapsedTimeSeconds = DateTime.Now.Subtract(Start).TotalMinutes;
379
380 if (ElapsedTimeSeconds > 60)
381 {
382 Log.Error("State change exceeds maximum allotted time.", Arguments.Machine.ObjectId,
383 new KeyValuePair<string, object>("StateMachineId", Arguments.Machine.StateMachineId),
384 new KeyValuePair<string, object>("ElapsedTimeSeconds", ElapsedTimeSeconds));
385
386 return;
387 }
388 }
389 }
390 }
391 while (!string.IsNullOrEmpty(StateId));
392 }
393 finally
394 {
395 await EvaluationComplete(Arguments);
396 }
397 }
398
404 public static async Task EvaluationComplete(EvaluationArguments Arguments)
405 {
406 bool StateUpdated = Arguments.StateUpdated;
407 bool VariablesUpdated = Arguments.VariablesUpdated;
408 bool AuthorizationsUpdated = Arguments.AuthorizationsUpdated;
409
410 if (StateUpdated || VariablesUpdated || AuthorizationsUpdated)
411 {
412 await Database.Update(Arguments.CurrentState);
413
414 Arguments.AuthorizationsUpdated = false;
415
416 if (StateUpdated)
417 {
418 Arguments.StateUpdated = false;
419
420 if (!(Arguments.Token is null))
421 {
422 StringBuilder Xml = new StringBuilder();
423 string OwnerJid = Arguments.Token.OwnerJid;
424
425 Xml.Append("<stateUpdated xmlns='");
427 Xml.Append("' tokenId='");
428 Xml.Append(XML.Encode(Arguments.Token.TokenId));
429 Xml.Append("' machineId='");
430 Xml.Append(XML.Encode(Arguments.Machine.StateMachineId));
431 Xml.Append("' state='");
432 Xml.Append(XML.Encode(Arguments.CurrentState.State));
433 Xml.Append("'/>");
434
435 await Arguments.EDaler.Server.SendMessage(string.Empty, string.Empty, Arguments.EDaler.MainDomain,
436 new XmppAddress(OwnerJid), string.Empty, Xml.ToString());
437
438 if (VariablesUpdated)
439 Xml.Clear();
440 }
441 }
442
443 if (VariablesUpdated)
444 {
445 if (Arguments.Token is null)
446 Arguments.ClearUpdatedVariables();
447 else
448 {
449 KeyValuePair<string, object>[] Variables = Arguments.PopUpdatedVariables();
450 StringBuilder Xml = new StringBuilder();
451 string OwnerJid = Arguments.Token.OwnerJid;
452
453 Xml.Append("<variablesUpdated xmlns='");
455 Xml.Append("' tokenId='");
456 Xml.Append(XML.Encode(Arguments.Token.TokenId));
457 Xml.Append("' machineId='");
458 Xml.Append(XML.Encode(Arguments.Machine.StateMachineId));
459 Xml.Append("'>");
460
461 foreach (KeyValuePair<string, object> P in Variables)
462 StateMachineProcessor.AppendVariable(Xml, P.Key, P.Value);
463
464 Xml.Append("</variablesUpdated>");
465
466 await Arguments.EDaler.Server.SendMessage(string.Empty, string.Empty, Arguments.EDaler.MainDomain,
467 new XmppAddress(OwnerJid), string.Empty, Xml.ToString());
468 }
469 }
470 }
471 }
472
477 public static async Task CheckConditionalEvents(EvaluationArguments Arguments)
478 {
479 if (!Arguments.Machine.TryGetState(Arguments.CurrentState.State, out State State))
480 return;
481
482 if (!(State.OnEvent is null))
483 {
484 foreach (OnEvent Event in State.OnEvent)
485 {
486 if (await Event.StateUpdated(Arguments))
487 {
488 string StateId;
489
490 try
491 {
492 await Event.ExecuteLog(Arguments);
493 StateId = await Event.GetNewState(Arguments);
494 }
495 catch (Exception ex)
496 {
497 StateId = await Event.GetFailureState(Arguments);
498
499 if (string.IsNullOrEmpty(StateId))
500 Log.Error(ex, Arguments.Machine.ObjectId);
501 }
502
503 if (!string.IsNullOrEmpty(StateId))
504 {
505 await GoToState(StateId, Arguments);
506 break;
507 }
508 }
509 }
510 }
511 }
512
518 public static async Task ExecuteLog(ActionReference[] ActionReferences, EvaluationArguments Arguments)
519 {
520 if (ActionReferences is null)
521 return;
522
523 foreach (ActionReference Action in ActionReferences)
524 await Action.ExecuteLog(Arguments);
525 }
526
533 public static async Task<IEnumerable<OnEvent>> RegisterEventHandlers(OnEvent[] Events, EvaluationArguments Arguments)
534 {
535 if (Events is null)
536 return null;
537
538 int i = 0;
539 LinkedList<OnEvent> Result = null;
540
541 foreach (OnEvent Event in Events)
542 {
543 if (await Event.Register(i++, Arguments))
544 {
545 if (Result is null)
546 Result = new LinkedList<OnEvent>();
547
548 Result.AddLast(Event);
549 }
550 }
551
552 return Result;
553 }
554
560 public static async Task UnregisterEventHandlers(OnEvent[] Events, EvaluationArguments Arguments)
561 {
562 IEnumerable<EventHandlers.EventHandler> Handlers = await Database.FindDelete<EventHandlers.EventHandler>(new FilterAnd(
563 new FilterFieldEqualTo("StateMachineId", Arguments.CurrentState.StateMachineId),
564 new FilterFieldEqualTo("State", Arguments.CurrentState.State)));
565
566 foreach (EventHandlers.EventHandler Handler in Handlers)
567 {
570 }
571
572 if (Events is null)
573 return;
574
575 int i = 0;
576
577 foreach (OnEvent Event in Events)
578 await Event.Unregister(i, Arguments);
579 }
580
587 public bool TryGetVariable(string Name, out Script.Variable Variable)
588 {
589 switch (Name)
590 {
591 case "this":
592 Variable = new Script.Variable("this", this);
593 return true;
594
595 default:
596 Variable = null;
597 return false;
598 }
599 }
600
606 public bool ContainsVariable(string Name)
607 {
608 return Name == "this";
609 }
610
616 public Waher.Script.Variable Add(string Name, object Value)
617 {
618 throw new NotSupportedException("Variable collection is read-only.");
619 }
620
624 public async Task<RequestOrigin> GetOrigin()
625 {
626 Token Token = await NeuroFeaturesProcessor.GetToken(this.CreatorTokenId, true);
627 string OwnerJid = Token?.OwnerJid.Value ?? string.Empty;
628
629 return new RequestOrigin(OwnerJid, null, null, null);
630 }
631
635 public void ReparseDefinition()
636 {
637 XmlDocument Doc = new XmlDocument();
638 Doc.LoadXml(this.XmlDefinition);
639
640 this.Root = (StateMachineRoot)StateMachineProcessor.Create(Doc.DocumentElement);
641
642 this.IndexElements();
643 }
644
649 public void SetObsoleteProperties(Dictionary<string, object> Properties)
650 {
651 foreach (KeyValuePair<string, object> Property in Properties)
652 {
653 switch (Property.Key)
654 {
655 case "XmlDefinnition":
656 if (Property.Value is string s)
657 this.XmlDefinition = s;
658 break;
659 }
660 }
661 }
662 }
663}
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
Class representing an event.
Definition: Event.cs:10
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 Critical(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a critical event.
Definition: Log.cs:1017
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
XmppAddress MainDomain
Main/principal domain address
Definition: Component.cs:86
XmppServer Server
XMPP Server.
Definition: Component.cs:96
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
override string ToString()
object.ToString()
Definition: XmppAddress.cs:190
Task< bool > SendMessage(string Type, string Id, string From, string To, string Language, string ContentXml)
Sends a Message stanza to a recipient.
Definition: XmppServer.cs:3412
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
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 Update(object Object)
Updates an object in the database.
Definition: Database.cs:626
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 conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
Class that keeps track of events and timing.
Definition: Profiler.cs:67
Contains information about a variable.
Definition: Variable.cs:10
Variable(string Name, IElement Value)
Contains information about a variable.
Definition: Variable.cs:19
Collection of variables.
Definition: Variables.cs:25
virtual Variable Add(string Name, object Value)
Adds a variable to the collection.
Definition: Variables.cs:122
Manages eDaler on accounts connected to the broker.
Marketplace processor, brokering sales of items via tenders and offers defined in smart contracts.
static int CalcArchiveDays(DateTime Expires, Duration? ArchiveReq, Duration? ArchiveOpt)
Calculates the number of days an object should be archived in the ledger.
Definition: Token.cs:582
CaseInsensitiveString OwnerJid
JID of Current owner of token
Definition: Token.cs:203
CaseInsensitiveString TokenId
Token ID
Definition: Token.cs:139
Class representing the current state of a state machine.
Definition: CurrentState.cs:20
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
Class representing a persisted state-machine variable value.
async Task RemoveFromCache()
Removes the event handler from the cache.
Abstract base class for nodes referencing an action.
async Task< TimeSpan > ExecuteLog(EvaluationArguments Arguments)
Evaluates an action, and logs the time the action took as a sample value.
Contains information required for evaluating script in a state-machine.
StateMachine Machine
Reference to state-machine definition.
bool AuthorizationsUpdated
If authorizations in the state machine have been updated.
KeyValuePair< string, object >[] PopUpdatedVariables()
Gets an array of updated variables since last call.
bool StateUpdated
If the state machine has changed state during processing.
bool VariablesUpdated
If variables in the state machine have been updated.
bool ForEach(ForEachCallback Callback, object State)
Iterates through th node and all its child nodes.
IStateMachineNode[] ChildNodes
Child nodes, if available. Null if no children.
Action executed when entering a state.
Definition: OnEvent.cs:17
async Task< string > GetFailureState(EvaluationArguments Arguments)
Gets the failure state ID when the event is triggered.
Definition: OnEvent.cs:124
async Task< string > GetNewState(EvaluationArguments Arguments)
Gets the new state ID when the event is triggered.
Definition: OnEvent.cs:111
OnEnter[] OnEnter
Events raised when entering the state.
Definition: State.cs:36
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 TryGetAction(string Id, out Model.Actions.Action Action)
Tries to get an action.
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.
bool TryGetVariable(string Name, out Script.Variable Variable)
Tries to get a variable object, given its name.
async Task< RequestOrigin > GetOrigin()
Origin of request.
void Index(Model.Events.Event Event)
Adds an event to the index.
static async Task GoToState(string StateId, EvaluationArguments Arguments)
Goes to a new state.
void SetObsoleteProperties(Dictionary< string, object > Properties)
Sets obsolete properties.
CaseInsensitiveString TrustProviderJid
JID of Trust Provider
Definition: StateMachine.cs:86
void ReparseDefinition()
Reparses the state-machine.
CaseInsensitiveString CreatorTokenId
ID of token that created the state-machine.
Definition: StateMachine.cs:56
static async Task ExecuteLog(ActionReference[] ActionReferences, EvaluationArguments Arguments)
Evaluates a set of actions.
bool TryGetEvent(string Id, out Model.Events.Event Event)
Tries to get an event.
async Task Start(EvaluationArguments Arguments)
Starts the processing of the state-machine.
string ObjectId
Object ID of state machine.
Definition: StateMachine.cs:46
void Index(State State)
Adds a state to the index.
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.
static async Task< IEnumerable< OnEvent > > RegisterEventHandlers(OnEvent[] Events, EvaluationArguments Arguments)
Registers a set of events.
Duration? ArchiveOptional
Duration after which token expires, and the required archiving time, the token can optionally be arch...
Definition: StateMachine.cs:98
CaseInsensitiveString DefinitionContractId
ID of Definition Contract
Definition: StateMachine.cs:76
void Index(Model.Actions.Action Action)
Adds an action to the index.
CaseInsensitiveString TrustProvider
ID of Trust Provider
Definition: StateMachine.cs:81
Waher.Script.Variable Add(string Name, object Value)
Adds a variable to the collection.
int ArchiveDays
Number of days to archive field.
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.
bool ContainsVariable(string Name)
If the collection contains a variable with a given name.
const string StateMachineNamespace
https://paiwise.tagroot.io/Schema/StateMachines.xsd
Class representing a sample of a state machine variable over time.
const string CurrentStateVariable
Variable ID used to sample state changes.
Tokens available in request.
Definition: RequestOrigin.cs:9
Variables available in a specific context.
Interface for requestors that can act as an origin for distributed requests.
TypeNameSerialization
How the type name should be serialized.
Represents a duration value, as defined by the xsd:duration data type: http://www....
Definition: Duration.cs:13