/// <summary> /// Checks whether the provided exception message is the end message or an exception message for the awaited <see cref="SendAndAwait"/> operation. /// </summary> /// <param name="message">The message to check.</param> /// <returns><c>true</c>, if the message was the end message or an exception message for the <see cref="SendAndAwait"/> operation.</returns> /// <remarks>For an example how to use this class see the type documentation.</remarks> public bool Check(Message message) { if (message == null) { throw new ArgumentNullException(nameof(message)); } if (!IsActive || !(message.Is <TEnd>() || message.Is <ExceptionMessage>())) { return(false); } MessageDomain domain = message.MessageDomain; while (domain?.IsTerminated == true) { domain = domain.Parent; } if (domain != null && continueActions.TryGetValue(domain, out OneTimeAction action)) { action.Execute(message); return(true); } return(false); }
public static MessageDomain GetMessageDomain(this Message[] domainSourceMessages) { if (domainSourceMessages.Length == 0) { return(MessageDomain.DefaultMessageDomain); } if (!domainSourceMessages[0].MessageDomain.IsTerminated && (domainSourceMessages.Length == 1 || domainSourceMessages.All(m => m.MessageDomain == domainSourceMessages[0].MessageDomain))) { return(domainSourceMessages[0].MessageDomain); } HashSet <MessageDomain> remainingDomains = new HashSet <MessageDomain>(GetDomainsFromMessages()); List <MessageDomain> visitedDomains = new List <MessageDomain>(); while (remainingDomains.Except(visitedDomains).Any()) { MessageDomain current = remainingDomains.Except(visitedDomains).First(); visitedDomains.Add(current); IEnumerable <MessageDomain> parentTree = FlattenParents(current); remainingDomains.ExceptWith(parentTree); } if (remainingDomains.Count != 1) { throw new InvalidOperationException("Cannot determine message domain from sibling domains."); } return(remainingDomains.Single()); IEnumerable <MessageDomain> FlattenParents(MessageDomain current) { MessageDomain parentDomain = current.Parent; while (parentDomain != null) { yield return(parentDomain); parentDomain = parentDomain.Parent; } } IEnumerable <MessageDomain> GetDomainsFromMessages() { foreach (MessageDomain messageDomain in domainSourceMessages.Select(m => m.MessageDomain)) { MessageDomain current = messageDomain; while (current?.IsTerminated == true) { current = messageDomain.Parent; } yield return(current ?? MessageDomain.DefaultMessageDomain); } } }
private static IEnumerable <MessageDomain> ThisAndFlattenedChildren(MessageDomain messageDomain) { yield return(messageDomain); foreach (MessageDomain child in messageDomain.Children.SelectMany(ThisAndFlattenedChildren)) { yield return(child); } }
internal void SwitchDomain(MessageDomain newDomain) { if (this != HeadMessage) { HeadMessage.SwitchDomain(newDomain); } MessageDomain = newDomain; foreach (Message descendant in Descendants) { descendant.MessageDomain = newDomain; } }
/// <summary> /// Overridden by inheriting classes to see if there is a completed set for the specified domain. /// </summary> /// <param name="domain">The domain which should be completed.</param> /// <param name="messageCollection">The message collection with the complete set.</param> /// <returns><c>true</c> if there is a completed set; otherwise <c>false</c></returns> protected virtual bool IsCompleted(MessageDomain domain, out MessageCollection messageCollection) { if (TryGetMessageFittingDomain(domain, Messages1, out MessageStore <T1> message1) && TryGetMessageFittingDomain(domain, Messages2, out MessageStore <T2> message2)) { messageCollection = new MessageCollection <T1, T2>(message1, message2, this); return(true); } messageCollection = null; return(false); }
/// <summary> /// Adds the message to this instance. /// </summary> /// <param name="message">The message to add to this instance.</param> /// <exception cref="ArgumentNullException">Thrown if the message is <c>null</c>.</exception> /// <exception cref="InvalidOperationException">Thrown if the <paramref name="message"/> is does not contain the type <typeparamref name="T"/>.</exception> public void Aggregate(Message message) { if (message == null) { throw new ArgumentNullException(nameof(message)); } if (!message.TryGet(out T aggregatedMessage)) { throw new InvalidOperationException($"Cannot aggregate the message {message}. Aggregated type is {typeof(T)}"); } IReadOnlyCollection <Message> root = aggregatedMessage.MessageDomain.SiblingDomainRootMessages; if (aggregatedMessage.MessageDomain.IsTerminated) { return; } HashSet <MessageStore <T> > completedMessageBatch = null; lock (dictionaryLock) { if (!aggregatedMessages.ContainsKey(root)) { aggregatedMessages.Add(root, new HashSet <MessageStore <T> >()); } aggregatedMessages[root].Add(aggregatedMessage); if (aggregatedMessages[root].Count == root.Count) { completedMessageBatch = aggregatedMessages[root]; aggregatedMessages.Remove(root); } } if (completedMessageBatch != null) { T[] messages = completedMessageBatch.Select <MessageStore <T>, T>(m => m).ToArray(); if (autoTerminate) { MessageDomain.TerminateDomainsOf(messages); } onAggregated(messages); foreach (MessageStore <T> messageStore in completedMessageBatch) { messageStore.Dispose(); } } }
/// <summary> /// Overridden by inheriting classes to get all sets of message for a specific domain without specific type. /// </summary> /// <param name="domain">The domain for which sets should be found.</param> /// <returns>An enumeration of all completed sets for the domain.</returns> /// <exception cref="ArgumentNullException">If the domain is null.</exception> protected IEnumerable <MessageCollection> GetCompleteSets(MessageDomain domain) { if (domain == null) { throw new ArgumentNullException(nameof(domain)); } HashSet <MessageCollection> sets = new HashSet <MessageCollection>(new MessageSetIdComparer()); foreach (MessageDomain messageDomain in ThisAndFlattenedChildren(domain).Where(d => !d.IsTerminated)) { if (IsCompleted(messageDomain, out MessageCollection set)) { sets.Add(set); } } return(sets); }
/// <summary> /// Used to get a message from the message dictionaries. /// </summary> /// <param name="domain">The message domain to get the message for.</param> /// <param name="messagePool">The dictionary for the specific message type.</param> /// <param name="message">The message for the domain.</param> /// <typeparam name="T">The type of the message.</typeparam> /// <returns><c>true</c> if the dictionary contains a message for the specific domain; otherwise <c>false</c>.</returns> /// <exception cref="ArgumentNullException">If the dictionary is null.</exception> protected bool TryGetMessageFittingDomain <T>(MessageDomain domain, ConcurrentDictionary <MessageDomain, MessageStore <T> > messagePool, out MessageStore <T> message) where T : Message { if (messagePool == null) { throw new ArgumentNullException(nameof(messagePool)); } MessageDomain current = domain; while (current != null) { if (messagePool.TryGetValue(current, out message)) { return(true); } current = current.Parent; } message = null; return(false); }
/// <summary> /// The method to send a start message and wait for the end message. /// </summary> /// <param name="startMessage">The start message.</param> /// <param name="onMessage">The action to send the message.</param> /// <param name="continueAction">The action to execute once a <see cref="MessageGateResult{TEnd}"/> was created.</param> /// <param name="timeout"> /// Optionally a timeout after which the method will return, without sending the result. /// By default the timeout is <see cref="NoTimout"/> /// </param> /// <param name="cancellationToken"> /// Optionally a cancellation token to cancel the continue operation. By default no CancellationToken will be used. /// </param> /// <remarks> /// <para>For an example how to use this class see the type documentation.</para> /// </remarks> public void SendAndContinue(TStart startMessage, Action <Message> onMessage, Action <MessageGateResult <TEnd> > continueAction, int timeout = NoTimout, CancellationToken cancellationToken = default) { if (startMessage == null) { throw new ArgumentNullException(nameof(startMessage)); } if (onMessage == null) { throw new ArgumentNullException(nameof(onMessage)); } if (continueAction == null) { throw new ArgumentNullException(nameof(continueAction)); } CancellationToken userCancelToken = cancellationToken; CancellationToken timeoutCancelToken = default; List <CancellationTokenSource> sources = new List <CancellationTokenSource>(); if (timeout != NoTimout) { CancellationTokenSource timeoutSource = new CancellationTokenSource(timeout); sources.Add(timeoutSource); timeoutCancelToken = timeoutSource.Token; } CancellationTokenSource combinedSource = CancellationTokenSource.CreateLinkedTokenSource(userCancelToken, timeoutCancelToken); cancellationToken = combinedSource.Token; sources.Add(combinedSource); MessageDomain.CreateNewDomainsFor(startMessage); CancellationTokenRegistration register = default; OneTimeAction action = new OneTimeAction(OnReceived); continueActions.TryAdd(startMessage.MessageDomain, action); register = cancellationToken.Register(OnCancel); onMessage(startMessage); void Dispose() { foreach (CancellationTokenSource tokenSource in sources) { tokenSource.Dispose(); } register.Dispose(); } void OnReceived(Message message) { if (!continueActions.TryRemove(startMessage.MessageDomain, out _)) { return; } MessageDomain.TerminateDomainsOf(startMessage); if (message.TryGet(out TEnd endMessage)) { continueAction(new MessageGateResult <TEnd>(MessageGateResultKind.Success, endMessage, Enumerable.Empty <ExceptionMessage>())); } else { ExceptionMessage exceptionMessage = message.Get <ExceptionMessage>(); continueAction( new MessageGateResult <TEnd>(MessageGateResultKind.Exception, null, new[] { exceptionMessage })); } Dispose(); } void OnCancel() { if (!continueActions.TryRemove(startMessage.MessageDomain, out _)) { return; } MessageDomain.TerminateDomainsOf(startMessage); MessageGateResultKind resultKind = MessageGateResultKind.Success; if (userCancelToken.IsCancellationRequested) { resultKind = MessageGateResultKind.Canceled; } else if (timeoutCancelToken.IsCancellationRequested) { resultKind = MessageGateResultKind.Timeout; } MessageGateResult <TEnd> result = new MessageGateResult <TEnd>(resultKind, null, Enumerable.Empty <ExceptionMessage>()); continueAction(result); Dispose(); } }