using Knoks.Core.Entities; using Knoks.Core.Logic.Interfaces; using Knoks.Framework.Content; using Knoks.Framework.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Knoks.Core.Logic.Managers { public class EventManager : IEventManager, IInitializable, IDisposable { private class EventInfo { public object Source { get; set; } public EventAction EventAction { get; set; } public object[] EventData { get; set; } public DateTime Timestamp { get; set; } public JObject EventArgs { get; set; } public EventInfo(object source, EventAction eventAction, object[] eventData, DateTime timestamp) { Source = source; EventAction = eventAction; EventData = eventData; Timestamp = timestamp; } } private readonly Dictionary> _ojectParsers = new Dictionary>(); private readonly Dictionary>> _eventDelegates = new Dictionary>>(); private readonly BlockingCollection _eventQueue = new BlockingCollection(); private Task _eventConsumingTask; private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; private T Get() { return _serviceProvider.GetRequiredService(); } public EventManager(ILogger logger, IServiceProvider serviceProvider) { _logger = logger; //Due to cross references (for example: UserManager) was decided to using DI on demand via IServiceProvider. //So, don't try to call IServiceProvider.GetService in constructot, //It will lead you to "Process is terminated due to StackOverflowException." //Instead use T Get() _serviceProvider = serviceProvider; } public Task Initialize() { return Task.Run(() => { InitObjectParsers(); InitEventDelegates(); _eventConsumingTask = Task.Factory.StartNew(() => { foreach (var eventInfo in _eventQueue.GetConsumingEnumerable()) { ProcessEvent(eventInfo); } }, TaskCreationOptions.LongRunning); }); } private void ProcessEvent(EventInfo eventInfo) { try { if (_eventDelegates.TryGetValue(eventInfo.EventAction, out IList> actions)) { if (actions.Count > 0) { eventInfo.EventArgs = JObject.FromObject(new { EventActionId = (int)eventInfo.EventAction, EventActionName = eventInfo.EventAction.ToString(), eventInfo.Timestamp }); foreach (var obj in eventInfo.EventData) { //if (!_ojectParsers.Keys.Contains(obj.GetType())) //{ // _ojectParsers.Add(obj.GetType(), null); //} //var o = (obj, eventInfo.EventArgs); _ojectParsers[obj.GetType()](obj, eventInfo.EventArgs); } } foreach (var action in actions) { Task.Run(() => { try { action(eventInfo.EventArgs); } catch (Exception ex) { _logger.LogError($"Process action - {eventInfo.EventAction} error.{Environment.NewLine}{ex}"); } }); } } } catch (Exception ex) { _logger.LogError($"Process event - {eventInfo.EventAction} error.{Environment.NewLine}{ex}"); } } public void Dispose() { try { if (_eventQueue != null) _eventQueue.CompleteAdding(); if (_eventConsumingTask != null) _eventConsumingTask.Wait(); } finally { if (_eventQueue != null) _eventQueue.Dispose(); } } public async Task FireЕvent(object source, EventAction eventAction, params object[] eventData) { await Task.Run(() => { var timestamp = Get().SystemDateTime; if (!FireЕventParams[eventAction].SequenceEqual(eventData.Select(i => i.GetType()))) throw new InvalidOperationException($"Unexpected parameters sequence or type(s). The expected format for '{eventAction}' is [{ string.Join(", ", FireЕventParams[eventAction].Select(i => i.FullName)) }]"); _eventQueue.Add(new EventInfo(source, eventAction, eventData, timestamp)); }); } //-- Example for multiple methods per EventAction variation -- //private static Dictionary FireЕventParams = new Dictionary //{ // [EventAction.LeadCreated] = new[] // { // new [] { typeof(LoanRequest), typeof(LoanRequest) }, // new [] { typeof(LoanRequest), typeof(LoanRequest) } // } //}; private static Dictionary FireЕventParams = new Dictionary { [EventAction.UserCreated] = new[] { typeof(User) }, [EventAction.ResetPassword] = new[] { typeof(UserPasswordReset) } }; private void ParseUser(long userId, JObject eventArgs) { var user = Get().GetUsers(userId).Result.Single(); _ojectParsers[user.GetType()](user, eventArgs); } private void ParseUser(string email, JObject eventArgs) { var user = Get().GetUserByEmail(email).Result; _ojectParsers[user.GetType()](user, eventArgs); } private void InitObjectParsers() { _ojectParsers.Add(typeof(User), (obj, eventArgs) => { var user = (User)obj; eventArgs["User"] = new JObject { ["UserId"] = user.UserId, ["Email"] = user.Email, ["FirstName"] = user.FirstName, ["LastName"] = user.LastName, ["UserEmail"] = user.Email, ["Phone"] = user.Phone, ["CountryId"] = user.CountryId, ["LanguageId"] = user.LanguageId, ["LanguageCode"] = "en" }; }); _ojectParsers.Add(typeof(UserPasswordReset), (obj, eventArgs) => { var userPasswordReset = (UserPasswordReset)obj; if (!string.IsNullOrEmpty(userPasswordReset.Email)) { ParseUser(userPasswordReset.Email, eventArgs); } eventArgs["UserPasswordReset"] = new JObject { ["Email"] = userPasswordReset.Email, ["Link"] = userPasswordReset.Link }; }); } private void InitEventDelegates() { //_eventDelegates.Add(EventAction.UserCreated, new List> //{ // (eventArgs) => //send email welcome // { // _logger.LogInformation($"############### Event type: {eventArgs["EventActionName"]} ##################"); // dynamic args = eventArgs; // var contentResult = Get().GenerateContent(new ContentSettings // { // Category = "Email", // Group = "welcome", // LanguageCode = args.User.LanguageCode, // Data = eventArgs // }); // } //}); _eventDelegates.Add(EventAction.ResetPassword, new List> { (eventArgs) => //send password reset email { _logger.LogInformation($"############### Event type: {eventArgs["EventActionName"]} ##################"); dynamic args = eventArgs; var contentResult = Get().GenerateContent(new ContentSettings { Category = "Email", Group = "resetPassword", LanguageCode = args.User.LanguageCode, Data = eventArgs }); Get().SendHtml( contentResult.Model["Subject"], contentResult.Content, null, eventArgs["UserPasswordReset"]["Email"].Value()); } }); } } }