Dalibor Votruba пре 2 година
родитељ
комит
e5ce7f9f5e

+ 45 - 0
Workbench/Processor/Dependencies/Data/Error.cs

@@ -0,0 +1,45 @@
+using System.Text;
+using Quadarax.Foundation.Core.Exceptions;
+
+namespace Quadarax.Foundation.Core.Data
+{
+    public class Error : IError
+    {
+        #region *** Properties ***
+        public string Message { get; }
+        public int Code { get; }
+        public ErrorLevelEnum Level { get; }
+
+        #endregion
+
+        #region *** Constructors ***
+        public Error(string message, int code, ErrorLevelEnum level)
+        {
+            Message = message;
+            Code = code;
+            Level = level;
+        }
+        public Error(Exception exception)
+        {
+            if (exception==null)
+                throw new ArgumentNullException(nameof(exception));
+
+            Message = exception.Message;
+            Code = exception.GetCode();
+        }
+        #endregion
+
+        #region *** Operations ***
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+            sb.Append("Error");
+            if (Code != 0)
+                sb.Append("[").Append(Code).Append("]");
+            sb.Append(":").Append(Message);
+            return sb.ToString();
+        }
+
+        #endregion
+    }
+}

+ 33 - 0
Workbench/Processor/Dependencies/Data/Extensions/PagingExt.cs

@@ -0,0 +1,33 @@
+namespace Quadarax.Foundation.Core.Data.Extensions
+{
+    public static class PagingExt
+    {
+        public static IQueryable<TEntity> Page<TEntity>(this IQueryable<TEntity> queryable, IPaging paging, int overlap = 0)
+        {
+            return queryable.Page(paging.IsDisabled ? null : paging.Page, paging.IsDisabled ? null :paging.PageSize, overlap);
+        }
+        public static IQueryable<TEntity> Page<TEntity>(this IQueryable<TEntity> queryable, int? page, int? pageSize, int overlap = 0)
+        {
+            if (!(page.HasValue && pageSize.HasValue))
+                return queryable;
+
+            pageSize = pageSize.GetValueOrDefault(int.MaxValue);
+
+            return queryable.Skip(page.Value * pageSize.Value).Take(pageSize.Value + overlap);
+        }
+
+        public static IEnumerable<TEntity> Page<TEntity>(this IEnumerable<TEntity> queryable, IPaging paging, int overlap = 0)
+        {
+            return queryable.Page(paging.IsDisabled ? null : paging.Page, paging.IsDisabled ? null :paging.PageSize, overlap);
+        }
+
+        public static IEnumerable<TEntity> Page<TEntity>(this IEnumerable<TEntity> queryable, int? page, int? pageSize, int overlap = 0)
+        {
+            if (!(page.HasValue && pageSize.HasValue))
+                return queryable;
+            pageSize = pageSize.GetValueOrDefault(int.MaxValue);
+
+            return queryable.Skip(page.Value * pageSize.Value).Take(pageSize.Value + overlap);
+        }
+    }
+}

+ 34 - 0
Workbench/Processor/Dependencies/Data/IError.cs

@@ -0,0 +1,34 @@
+namespace Quadarax.Foundation.Core.Data
+{
+
+    /// <summary>
+    /// Defines error message structure
+    /// </summary>
+    public interface IError
+    {
+        /// <summary>
+        /// Full error message
+        /// </summary>
+        public string Message { get; }
+        /// <summary>
+        /// Error message short numeric code
+        /// </summary>
+        public int Code { get; }
+        /// <summary>
+        /// Defines error level
+        /// </summary>
+        public ErrorLevelEnum Level { get; }
+    }
+
+    /// <summary>
+    /// Error levels enumeration
+    /// </summary>
+    public enum ErrorLevelEnum
+    {
+        Note,
+        Warning,
+        Error,
+        Critical,
+        Fatal
+    }
+}

+ 9 - 0
Workbench/Processor/Dependencies/Data/IPaging.cs

@@ -0,0 +1,9 @@
+namespace Quadarax.Foundation.Core.Data
+{
+    public interface IPaging
+    {
+        int Page { get; }
+        int PageSize { get; }
+        bool IsDisabled { get; }
+    }
+}

+ 23 - 0
Workbench/Processor/Dependencies/Data/IResult.cs

@@ -0,0 +1,23 @@
+namespace Quadarax.Foundation.Core.Data
+{
+    /// <summary>
+    /// Represents structure of result state
+    /// </summary>
+    public interface IResult
+    {
+        /// <summary>
+        /// Gets if result is logically success
+        /// </summary>
+        bool IsSuccess { get; }
+        /// <summary>
+        /// Collection of error items
+        /// </summary>
+        public IEnumerable<IError> Errors { get; }
+
+        /// <summary>
+        /// Returns result value
+        /// </summary>
+        /// <returns>Value</returns>
+        public object? GetValue();
+    }
+}

+ 5 - 0
Workbench/Processor/Dependencies/Data/ITypedArgument.cs

@@ -0,0 +1,5 @@
+namespace Quadarax.Foundation.Core.Data;
+
+public interface ITypedArgument
+{
+}

+ 32 - 0
Workbench/Processor/Dependencies/Data/Paging.cs

@@ -0,0 +1,32 @@
+using System.Text;
+
+namespace Quadarax.Foundation.Core.Data
+{
+    public class Paging : IPaging
+    {
+        #region *** Properties ***
+        public int Page { get; protected set; }
+        public int PageSize { get; protected set; }
+        public bool IsDisabled { get; protected set; }
+        public static IPaging NoPaging => new Paging(0, 0);
+        #endregion
+
+        #region *** Constructors ***
+        public Paging(int page, int pageSize)
+        {
+            Page = page;
+            PageSize = pageSize;
+            IsDisabled = (PageSize == 0);
+        }
+        #endregion
+
+        #region *** Operations ***
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+            sb.Append("Paging:").Append(IsDisabled ? "Disabled" : "Enabled").Append(";Page=").Append(Page).Append(";PageSize=").Append(PageSize);
+            return sb.ToString();
+        }
+        #endregion
+    }
+}

+ 58 - 0
Workbench/Processor/Dependencies/Data/Result.cs

@@ -0,0 +1,58 @@
+using Quadarax.Foundation.Core.Value.Extensions;
+
+namespace Quadarax.Foundation.Core.Data
+{
+    public class Result : IResult
+    {
+        #region *** Properties ***
+        public bool IsSuccess { get; protected set; }
+        public IEnumerable<IError> Errors => _errors;
+        public static IResult Ok => new Result();
+        public static IResult Fail => new Result(false, new List<IError> { new Error("Operation failed", 0, ErrorLevelEnum.Error) });
+        #endregion
+
+        #region *** Private Fields ***
+        private readonly IList<IError> _errors = new List<IError>();
+        #endregion
+
+        #region *** Constructors ***
+
+        public Result() : this(true, null)
+        {
+        }
+        public Result(IEnumerable<IError> errors) : this(false, errors)
+        {
+        }
+        public Result(bool isSuccess, IEnumerable<IError>? errors)
+        {
+            errors ??= new List<IError>();
+            IsSuccess = isSuccess;
+            _errors.AddRange(errors);
+        }
+        #endregion
+
+        #region *** Operations ***
+        public void SetSuccess(bool isSuccess)
+        {
+            IsSuccess = isSuccess;
+        }
+        public void AddError(IError error)
+        {
+            if (error == null)
+                throw new ArgumentNullException(nameof(error));
+
+            _errors.Add(error);
+        }
+
+        public void ClearErrors()
+        {
+            _errors.Clear();
+        }
+
+        public object? GetValue()
+        {
+            return null;
+        }
+        #endregion
+    }
+}

+ 2 - 2
Workbench/Processor/Dependencies/Data/TypedArgument.cs

@@ -1,8 +1,8 @@
 using System;
 
-namespace Quadarax.Foundation.Core.Object;
+namespace Quadarax.Foundation.Core.Data;
 
-public class TypedArgument
+public class TypedArgument : ITypedArgument
 {
     public string Name {get; private set;}
     public string? Value {get; private set;}  //TODO: must be nullable

+ 20 - 0
Workbench/Processor/Dependencies/Exceptions/ExceptionExc.cs

@@ -16,5 +16,25 @@ namespace Quadarax.Foundation.Core.Exceptions
         {
             e.Data.Add("Code", code);
         }
+
+        public static string Dump(this Exception e)
+        {
+            var level = 0;
+            var sb = new System.Text.StringBuilder();
+            sb.Append("[#").Append(level).Append("]:").AppendLine(e.Message);
+            var inner = e.InnerException;
+            while (inner != null)
+            {
+                sb.Append("[#").Append(level).Append("]:").AppendLine(inner.Message);
+                inner = inner.InnerException;
+            }
+
+            if (e.StackTrace != null)
+            {
+                sb.AppendLine("[#0] Stack list:");
+                sb.AppendLine(e.StackTrace);
+            }
+            return sb.ToString();
+        }
     }
 }

+ 10 - 5
Workbench/Processor/Exceptons/TaskValidityExpiredException.cs

@@ -4,16 +4,19 @@ namespace Quadarax.Foundation.Core.Business.Processor.Exceptons
 {
     public class TaskValidityExpiredException : Exception
     {
-        
+
         #region *** Constants ***
+
         private const string CS_EXCC_ENT_TYPE = "EntityType";
         private const string CS_EXCC_ENT_ID = "EntityIdentifier";
         private const string CS_EXCC_ENT_CURRENT_STA = "CurrentStatus";
         private const string CS_EXCC_ENT_VALFROM = "ValidFrom";
         private const string CS_EXCC_ENT_VALTO = "ValidTo";
+
         #endregion
 
         #region *** Constructors ***
+
         public TaskValidityExpiredException(ITaskItem task) : base(GetMessage(task))
         {
             Data.Add(CS_EXCC_ENT_TYPE, task.GetType());
@@ -21,17 +24,19 @@ namespace Quadarax.Foundation.Core.Business.Processor.Exceptons
             Data.Add(CS_EXCC_ENT_CURRENT_STA, task.Status);
             Data.Add(CS_EXCC_ENT_VALFROM, task.ProcessingValidFrom);
             Data.Add(CS_EXCC_ENT_VALTO, task.ProcessingValidTo.GetValueOrDefault(DateTime.MaxValue));
-            
+
         }
 
         #endregion
 
         #region *** Private Operations ***
+
         private static string GetMessage(ITaskItem task)
         {
-            return $"Task validity expired. Task: {task.GetType()}[{task.Id}] is out of validity ({task.ProcessingValidFrom} - {task.ProcessingValidTo.GetValueOrDefault(DateTime.MaxValue)})";
+            return
+                $"Task validity expired. Task: {task.GetType()}[{task.Id}] is out of validity ({task.ProcessingValidFrom} - {task.ProcessingValidTo.GetValueOrDefault(DateTime.MaxValue)})";
         }
-        
+
         #endregion
-        
+    }
 }

+ 5 - 1
Workbench/Processor/Providers/IProcessorStorageProvider.cs

@@ -1,5 +1,7 @@
 using Quadarax.Foundation.Core.Business.Processor.Enums;
 using Quadarax.Foundation.Core.Business.Processor.Task;
+using Quadarax.Foundation.Core.Data;
+using Quadarax.Foundation.Core.Object;
 
 namespace Quadarax.Foundation.Core.Business.Processor.Providers
 {
@@ -42,11 +44,13 @@ namespace Quadarax.Foundation.Core.Business.Processor.Providers
         /// </summary>
         /// <param name="ids">Storage identifiers of Task item</param>
         public void Delete(params ITaskIdentifier[] ids);
+
         /// <summary>
         /// Query task items from storage by status.
         /// </summary>
+        /// <param name="paging">Defines paging settings to apply in storage.</param>
         /// <param name="statuses">Allowed statuses</param>
         /// <returns>Queryable collection of selected Task items.</returns>
-        public IQueryable<ITaskItemData> Query(params ProcessStatusEnum[] statuses);
+        public IQueryable<ITaskItemData> Query(IPaging paging, params ProcessStatusEnum[] statuses);
     }
 }

+ 6 - 2
Workbench/Processor/Providers/InMemoryStorageProvider.cs

@@ -1,6 +1,10 @@
 using System.Collections.Concurrent;
+using Quadarax.Foundation.Core.Business.Processor.Dependencies.Value.Extensions;
 using Quadarax.Foundation.Core.Business.Processor.Enums;
 using Quadarax.Foundation.Core.Business.Processor.Task;
+using Quadarax.Foundation.Core.Data;
+using Quadarax.Foundation.Core.Data.Extensions;
+using Quadarax.Foundation.Core.Object;
 
 namespace Quadarax.Foundation.Core.Business.Processor.Providers
 {
@@ -16,9 +20,9 @@ namespace Quadarax.Foundation.Core.Business.Processor.Providers
             _cache.Clear();
         }
 
-        public override IQueryable<ITaskItemData> Query(params ProcessStatusEnum[] statuses)
+        public override IQueryable<ITaskItemData> Query(IPaging paging, params ProcessStatusEnum[] statuses)
         {
-            throw new NotImplementedException();
+            return _cache.Values.Where(x=>x.Status.In(statuses)).AsQueryable().Page(paging);
         }
 
         protected override void OnOpen()

+ 2 - 1
Workbench/Processor/Providers/ProcessorStorageProvider.cs

@@ -1,6 +1,7 @@
 
 using Quadarax.Foundation.Core.Business.Processor.Enums;
 using Quadarax.Foundation.Core.Business.Processor.Task;
+using Quadarax.Foundation.Core.Data;
 using Quadarax.Foundation.Core.Logging;
 using Quadarax.Foundation.Core.Object;
 
@@ -111,7 +112,7 @@ public abstract class ProcessorStorageProvider : DisposableObject, IProcessorSto
         }
     }
 
-    public abstract IQueryable<ITaskItemData> Query(params ProcessStatusEnum[] statuses);
+    public abstract IQueryable<ITaskItemData> Query(IPaging paging, params ProcessStatusEnum[] statuses);
 
     #region *** Private Operations & Virtuals ***
     protected virtual void Log(LogSeverityEnum type, string message, Exception? e = null)

+ 9 - 12
Workbench/Processor/Queue/ProcessQueue.cs

@@ -1,11 +1,7 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Quadarax.Foundation.Core.Business.Processor.Enums;
+using Quadarax.Foundation.Core.Business.Processor.Enums;
 using Quadarax.Foundation.Core.Business.Processor.Providers;
 using Quadarax.Foundation.Core.Business.Processor.Task;
+using Quadarax.Foundation.Core.Data;
 using Quadarax.Foundation.Core.Logging;
 using Quadarax.Foundation.Core.Object;
 using Quadarax.Foundation.Core.Value.Extensions;
@@ -130,7 +126,7 @@ namespace Quadarax.Foundation.Core.Business.Processor.Queue
             return (ITaskItem[])result;
         }
 
-        public ITaskIdentifier[] QueryTasks(ProcessStatusEnum[] states,int maxItems, bool affinityQueue = true, bool onlyTimeValid = true, bool includeDeleted = false)
+        public ITaskIdentifier[] QueryTasks(IPaging paging,ProcessStatusEnum[] states,int maxItems, bool affinityQueue = true, bool onlyTimeValid = true, bool includeDeleted = false)
         {
             if (states == null || !states.Any()) throw new ArgumentNullException(nameof(states));
 
@@ -138,7 +134,7 @@ namespace Quadarax.Foundation.Core.Business.Processor.Queue
             if (!states.Contains(ProcessStatusEnum.Deleted) && includeDeleted)
                 states = states.Concat(new[] { ProcessStatusEnum.Deleted }).ToArray();
 
-            var items = _storageProvider.Query(states);
+            var items = _storageProvider.Query(paging, states);
             if (onlyTimeValid)
                 items = items.Where(x=> x.ProcessingValidFrom <= now && x.ProcessingValidTo.GetValueOrDefault(DateTime.MaxValue) >= now);
             
@@ -168,7 +164,7 @@ namespace Quadarax.Foundation.Core.Business.Processor.Queue
                 {
                     // first cycle scenario
                     // reset previous left tasks
-                    var forceLeftTaskIds = QueryTasks(new[] { ProcessStatusEnum.Started}, _configuration.WorkersCount, true,true, false);
+                    var forceLeftTaskIds = QueryTasks(Paging.NoPaging, new[] { ProcessStatusEnum.Started}, _configuration.WorkersCount, true,true, false);
                     var forceLeftTasks = GetTask(forceLeftTaskIds);
                     Log(LogSeverityEnum.Debug, $"ProcessQueue [{QueueIdentifier}] Watchdog first cycle. Force reset on {forceLeftTaskIds.Count()} tasks.");
                     foreach (var task in forceLeftTasks)
@@ -180,14 +176,15 @@ namespace Quadarax.Foundation.Core.Business.Processor.Queue
                 }
 
                 // gets processing tasks to know thread assigned slots
-                var assigningTaskIds = QueryTasks(new[] { ProcessStatusEnum.Pending }, _taskSlots.FreeSlotsCount, false, true, false);
+                var assigningTaskIds = QueryTasks(Paging.NoPaging, new[] { ProcessStatusEnum.Pending }, _taskSlots.FreeSlotsCount, false, true, false);
                 var assigningTasks = GetTask(assigningTaskIds);
                 var startedCnt = 0;
                 foreach (var assign in assigningTasks)
                 {
-                    if (_taskSlots.AssignSlot(assign) >= 0)
+                    var ordinal = _taskSlots.AssignSlot(assign);
+                    if (ordinal >= 0)
                     {
-                        ((TaskItem)assign).Start();
+                        ((TaskItem)assign).Start(new AffinityToken(QueueIdentifier, ordinal));
                         startedCnt++;
                     }
                 }

+ 9 - 0
Workbench/Processor/Task/ITaskExecuteProvider.cs

@@ -0,0 +1,9 @@
+using Quadarax.Foundation.Core.Object;
+
+namespace Quadarax.Foundation.Core.Business.Processor.Task
+{
+    internal interface ITaskExecuteProvider
+    {
+        IResult Execute(ITypedArgument[] arguments);
+    }
+}

+ 12 - 0
Workbench/Processor/Task/TaskExecuteProvider.cs

@@ -0,0 +1,12 @@
+using Quadarax.Foundation.Core.Object;
+
+namespace Quadarax.Foundation.Core.Business.Processor.Task
+{
+    public abstract class TaskExecuteProvider : ITaskExecuteProvider
+    {
+        public IResult Execute(ITypedArgument[] arguments)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 23 - 0
Workbench/Processor/Task/TaskExecuteProviderFactory.cs

@@ -0,0 +1,23 @@
+using System.Reflection;
+using Quadarax.Foundation.Core.Object;
+using Quadarax.Foundation.Core.Reflection;
+
+namespace Quadarax.Foundation.Core.Business.Processor.Task
+{
+    internal class TaskExecuteProviderFactory
+    {
+
+        public TaskExecuteProviderFactory()
+        {
+
+        }
+
+        public ITaskExecuteProvider Create(string providerClass, ITypedArgument[] arguments)
+        {
+            var type = TypeUtils.GetRemoteType(providerClass);
+            if (type == null) throw new Exception($"Cannot find type {providerClass}");
+            
+            return (ITaskExecuteProvider)Activator.CreateInstance(type, arguments);
+        }
+    }
+}

+ 46 - 9
Workbench/Processor/Task/TaskItem.cs

@@ -3,6 +3,7 @@ using System.Threading;
 using Quadarax.Foundation.Core.Business.Processor.Enums;
 using Quadarax.Foundation.Core.Business.Processor.Exceptons;
 using Quadarax.Foundation.Core.Business.Processor.Queue;
+using Quadarax.Foundation.Core.Data;
 using Quadarax.Foundation.Core.Logging;
 using Quadarax.Foundation.Core.Object;
 using Quadarax.Foundation.Core.Value.Extensions;
@@ -104,7 +105,7 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
             this(parent, reference, source, initialArguments, priority, attepts, attemptTimeout, validFrom, validTo, initialStatusLog)
         {
             Id = id;
-            Status = initialStatus;
+            SetStatus(initialStatus, "(System) Initialization");
         }
         public TaskItem(ProcessQueue parent, string reference, string providerClass, TypedArgument[] initialArguments, byte priority, int attepts, TimeSpan? attemptTimeout, DateTime validFrom, DateTime? validTo, ITaskStatusLogItem[]? initialStatusLog)
         {	
@@ -123,7 +124,7 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
             Created = DateTime.Now;
             if (initialStatusLog != null)
                 _statusLog.Setup(initialStatusLog);
-            Status = ProcessStatusEnum.Pending; 
+            SetStatus(ProcessStatusEnum.Pending, "(System) Initialization");
             Id = long.MinValue;
         }
         #endregion
@@ -226,9 +227,37 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
 
         internal void SetStatus(ProcessStatusEnum status, string reason)
         {
-            if (status == Status) return;
+            if (status == Status)
+            {
+                var log = _statusLog.Where(x => x.Status == status).MaxBy(x => x.Created);
+                if (log == null) throw new InvalidOperationException($"Inconsistent task state. Status log must not be empty for status '{status}'.");
+                if (log.Reason != reason)
+                {
+                    ((TaskStatusLogItem)log).Reason = reason;
+                    Changed = DateTime.Now;
+                    return;
+                }
+            }
+
+            _statusLog.Add(new TaskStatusLogItem(status, reason));
+
+            switch (status)
+            {
+                case ProcessStatusEnum.Initial:
+                    Created = DateTime.Now;
+                    break;
+                case ProcessStatusEnum.Started:
+                    StatStarted = DateTime.Now;
+                    break;
+                case ProcessStatusEnum.ProcessedFail:
+                case ProcessStatusEnum.ProcessedSucc:
+                case ProcessStatusEnum.ExpiredAttemts:
+                case ProcessStatusEnum.ExpiredValidity:
+                case ProcessStatusEnum.Failed:
+                    StatFinished = DateTime.Now;
+                    break;
+            }
 
-            _statusLog.Add(new TaskStatusLogItem(Status, reason));
             Status = status; 
             Changed = DateTime.Now;
         }
@@ -312,13 +341,21 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
 
         private void Execute(object? state)
         {
-            if (state==null) throw new ArgumentNullException(nameof(state));
-            var token = (CancellationTokenSource)state;
+            try
+            {
+                if (state==null) throw new ArgumentNullException(nameof(state));
+                var token = (CancellationTokenSource)state;
 
-            SetStatus(ProcessStatusEnum.Started, $"(System) Task started with attempt #{(InitialAttempts - AttemptsLeft) + 1}");
-            StatStarted = DateTime.Now;
-            _parent.Provider.Set(this);
+                SetStatus(ProcessStatusEnum.Started, $"(System) Task started with attempt #{(InitialAttempts - AttemptsLeft) + 1}");
+                StatStarted = DateTime.Now;
+                _parent.Provider.Set(this);
 
+            }
+            catch (Exception e)
+            {
+                
+            }
+            
             
         }
 

+ 1 - 1
Workbench/Processor/Task/TaskStatusLogItem.cs

@@ -6,7 +6,7 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
     {
         #region *** Public Properties ***
         public ProcessStatusEnum Status { get; }
-        public string Reason { get; }
+        public string Reason { get; internal set; }
         public DateTime Created { get; }
         #endregion