using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics.CodeAnalysis;
using log4net;
using System.Globalization;
namespace MT.Platform.Common
{
///
/// Notification Broker to broadcast local and/or remote notifications.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")]
public static class NotificationBroker
{
#region [auto] Fields
///
/// Writes to logging infrastructure.
///
private static readonly ILog logger = LogManager.GetLogger(typeof(NotificationBroker));
///
/// An identifier to distinguish instances of NB (client, server),
/// so not to sound notifications in circles. To check if process name is sufficient.
///
private static string id = System.Diagnostics.Process.GetCurrentProcess().ProcessName.Replace(".vshost", "");
///
/// List of subscriptions
///
private static Subscriptions subscriptions = new Subscriptions();
#endregion [auto] Fields
#region [auto] Properties
///
/// NotificationBroker identification used as sender. Do not dispatch notification sent yourself.
///
public static string Id { get { return id; } }
#endregion [auto] Properties
#region [auto] Methods
///
/// Remove and disposes all subscriptions.
///
public static void Clear()
{
// remove all local ones
subscriptions.Clear();
}
///
/// Registers a notification subscription (including type, handlers and filters).
///
/// The subscription (local, web service, db, etc.) to register.
public static void Register(Subscription subscription)
{
lock (subscriptions)
{
subscriptions.Add(subscription);
}
}
///
/// Registers a notification subscription (including type, handlers and filters).
///
/// The type of the message to register for.
/// The handler to invoke when a message of the given type arrives.
/// The filters that apply for this subscription so not to get certain messages.
public static void Register(Type type, NotificationHandler handler, IList filters)
{
LocalSubscription subscription = new LocalSubscription(type, handler, filters);
lock (subscriptions)
{
subscriptions.Add(subscription);
}
}
///
/// Publishes a notification message.
///
/// The notification message.
/// The scope of the notification (Local or Remote/Local).
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes"), SuppressMessage("Microsoft.Design", "CA1030", Justification = "there are no events in a WCF contract")]
public static void Send(Notification notification, NotificationScope scope)
{
// test inputs
//Validate.IsNotNull(notification, "Must pass a valid notification");
// adds the sender (current named instance of NB) to the notification
// to find out that a certain notification was sent by yourself!
if (string.IsNullOrEmpty(notification.Sender))
{
// only the initial sender is important. Do not overwrite by intermediary service callback
// implementations calling this Send() too.
notification.Sender = Id;
}
// fire locally. Get a copied list of subscription references.
lock (subscriptions)
{
IList copiedSubscriptions = subscriptions.Get(notification.GetType());
foreach (Subscription subscription in copiedSubscriptions)
{
try
{
// fire when no filter specified
if (subscription.Filters == null)
{
SendAsync(subscription, notification, scope);
continue;
}
if (subscription.Filters.Count == 0)
{
SendAsync(subscription, notification, scope);
continue;
}
// fire when one of the filters evaluates to true
bool filterIncluded = true;
bool filterExcluded = false;
foreach (ISubscriptionFilter subscriptionFilter in subscription.Filters)
{
// fire when one (and only when; not and/or logic) filter include evaluates true
if (subscriptionFilter.FilterOperation == SubscriptionFilterOperation.Include
&& !subscriptionFilter.IsMatch(notification))
{
filterIncluded = false; // filter out
}
// however, does not fire when one (and only when; not and/or logic) filter exclude evaluates true
if (subscriptionFilter.FilterOperation == SubscriptionFilterOperation.Exclude
&& subscriptionFilter.IsMatch(notification))
{
filterExcluded = true; // filter out
}
if (filterIncluded && !filterExcluded)
{
SendAsync(subscription, notification, scope);
}
}
}
catch (Exception ex)
{
logger.Warn("One of the subscription handler invocations failed.", ex);
}
}
}
}
///
/// Unregisters a notification subscription.
///
/// The subscription to remove (local, web service, db, etc.).
public static void Unregister(Subscription subscription)
{
lock (subscriptions)
{
subscriptions.Remove(subscription);
}
}
///
/// Unregisters a notification subscription.
///
/// The type of the subscription to remove.
/// The handler that was registered to receive notifications.
public static void Unregister(Type type, NotificationHandler handler)
{
lock (subscriptions)
{
subscriptions.Remove(type, handler);
}
}
///
/// Unregisters the specified type.
///
/// The type of message to unregister.
public static void Unregister(Type type)
{
lock (subscriptions)
{
subscriptions.Remove(type);
}
}
///
/// Asynchronously send notification using a thread pool thread.
///
/// The subscription.
/// The notification.
/// The scope of the notification.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
private static void SendAsync(Subscription subscription, Notification notification, NotificationScope scope)
{
if (subscription.Scope == scope || scope == NotificationScope.Remote)
{
// go to another thread to execute delegate!
ThreadPool.QueueUserWorkItem(
delegate(object state)
{
try
{
// invoke one separate invocation handler on pool thread to decouple.
subscription.Send(notification);
}
catch (Exception ex)
{
logger.Warn(string.Format(CultureInfo.InvariantCulture, "{0} subscription handler {1} invocations failed.", subscription.Scope, subscription.NotificationType.FullName), ex);
}
}, null);
}
}
#endregion [auto] Methods
}
}