RulesEngine/src/RulesEngine/RulesEngine/RulesEngine.cs

273 lines
9.8 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.HelperFunctions;
using RulesEngine.Interfaces;
using RulesEngine.Models;
using RulesEngine.Validators;
using RulesEngine.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using FluentValidation;
namespace RulesEngine
{
public class RulesEngine : IRulesEngine
{
#region Variables
private Dictionary<string, object> compileRulesDic;
private Dictionary<string, WorkflowRules> workflowRulesDic;
private readonly ILogger _logger;
private readonly ReSettings _reSettings;
#endregion
#region Constructor
public RulesEngine(string[] jsonConfig, ILogger logger, ReSettings reSettings = null) : this(logger, reSettings)
{
var workflowRules = jsonConfig.Select(item => JsonConvert.DeserializeObject<WorkflowRules>(item)).ToArray();
AddWorkflow(workflowRules);
}
public RulesEngine(WorkflowRules[] workflowRules, ILogger logger, ReSettings reSettings = null) : this(logger, reSettings)
{
AddWorkflow(workflowRules);
}
public RulesEngine(ILogger logger, ReSettings reSettings = null)
{
_logger = logger ?? new NullLogger();
_reSettings = reSettings ?? new ReSettings();
InitializeVariables();
}
#endregion
#region Public Methods
/// <summary>
/// This will execute all the rules of the specified workflow
/// </summary>
/// <typeparam name="T">type of input</typeparam>
/// <param name="input">input</param>
/// <param name="workflowName">Workflow Name</param>
/// <returns>List of Result</returns>
public List<RuleResultTree> ExecuteRule(string workflowName, IEnumerable<dynamic> input, object[] otherInputs)
{
_logger.LogTrace($"Called ExecuteRule for workflow {workflowName} and count of input {input.Count()}");
var result = new List<RuleResultTree>();
foreach (var item in input)
{
var ruleInputs = new List<object>();
ruleInputs.Add(item);
if (otherInputs != null)
ruleInputs.AddRange(otherInputs);
result.AddRange(ExecuteRule(workflowName, ruleInputs.ToArray()));
}
return result;
}
public List<RuleResultTree> ExecuteRule(string workflowName, object[] inputs)
{
var ruleParams = new List<RuleParameter>();
for (int i = 0; i < inputs.Length; i++)
{
var input = inputs[i];
var obj = Utils.GetTypedObject(input);
ruleParams.Add(new RuleParameter($"input{i + 1}", obj));
}
return ExecuteRule(workflowName, ruleParams.ToArray());
}
public List<RuleResultTree> ExecuteRule(string workflowName, object input)
{
var inputs = new[] { input };
return ExecuteRule(workflowName, inputs);
}
public List<RuleResultTree> ExecuteRule(string workflowName, RuleParameter[] ruleParams)
{
return ValidateWorkflowAndExecuteRule(workflowName, ruleParams);
}
#endregion
#region Private Methods
/// <summary>
/// This is for Initializing the variables
/// </summary>
private void InitializeVariables()
{
if (compileRulesDic == null)
compileRulesDic = new Dictionary<string, object>();
if (workflowRulesDic == null)
workflowRulesDic = new Dictionary<string, WorkflowRules>();
}
public void AddWorkflow(params WorkflowRules[] workflowRules)
{
try
{
foreach (var workflowRule in workflowRules)
{
var validator = new WorkflowRulesValidator();
validator.ValidateAndThrow(workflowRule);
if (!workflowRulesDic.ContainsKey(workflowRule.WorkflowName))
{
workflowRulesDic[workflowRule.WorkflowName] = workflowRule;
}
else
{
throw new ArgumentException($"Workflow with name: {workflowRule} already exists");
}
}
}
catch (ValidationException ex)
{
throw new RuleValidationException(ex.Message, ex.Errors);
}
}
public void ClearWorkflows()
{
workflowRulesDic.Clear();
compileRulesDic.Clear();
}
public void RemoveWorkflow(params string[] workflowNames)
{
foreach (var workflowName in workflowNames)
{
workflowRulesDic.Remove(workflowName);
var compiledKeysToRemove = compileRulesDic.Keys.Where(key => key.StartsWith(workflowName));
foreach(var key in compiledKeysToRemove)
{
compileRulesDic.Remove(key);
}
}
}
/// <summary>
/// This will validate workflow rules then call execute method
/// </summary>
/// <typeparam name="T">type of entity</typeparam>
/// <param name="input">input</param>
/// <param name="workflowName">workflow name</param>
/// <returns>list of rule result set</returns>
private List<RuleResultTree> ValidateWorkflowAndExecuteRule(string workflowName, RuleParameter[] ruleParams)
{
List<RuleResultTree> result;
if (RegisterRule(workflowName, ruleParams))
{
result = ExecuteRuleByWorkflow(workflowName, ruleParams);
}
else
{
_logger.LogTrace($"Rule config file is not present for the {workflowName} workflow");
// if rules are not registered with Rules Engine
throw new ArgumentException($"Rule config file is not present for the {workflowName} workflow");
}
return result;
}
/// <summary>
/// This will compile the rules and store them to dictionary
/// </summary>
/// <typeparam name="T">type of entity</typeparam>
/// <param name="workflowName">workflow name</param>
/// <returns>bool result</returns>
private bool RegisterRule(string workflowName, params RuleParameter[] ruleParams)
{
string compileRulesKey = GetCompileRulesKey(workflowName, ruleParams);
if (compileRulesDic.ContainsKey(compileRulesKey))
return true;
var workflowRules = GetWorkFlowRules(workflowName);
if (workflowRules != null)
{
var lstFunc = new List<Delegate>();
foreach (var rule in workflowRulesDic[workflowName].Rules)
{
RuleCompiler ruleCompiler = new RuleCompiler(new RuleExpressionBuilderFactory(_reSettings), _logger);
lstFunc.Add(ruleCompiler.CompileRule(rule, ruleParams));
}
compileRulesDic.Add(compileRulesKey, new CompiledRule() { CompiledRules = lstFunc });
_logger.LogTrace($"Rules has been compiled for the {workflowName} workflow and added to dictionary");
return true;
}
else
{
return false;
}
}
private WorkflowRules GetWorkFlowRules(string workflowName)
{
workflowRulesDic.TryGetValue(workflowName, out var workflowRules);
if (workflowRules == null) return null;
else
{
if (workflowRules.WorkflowRulesToInject?.Any() == true)
{
if (workflowRules.Rules == null)
{
workflowRules.Rules = new List<Rule>();
}
foreach (string wfname in workflowRules.WorkflowRulesToInject)
{
var injectedWorkflow = GetWorkFlowRules(wfname);
if (injectedWorkflow == null)
{
throw new Exception($"Could not find injected Workflow: {wfname}");
}
workflowRules.Rules.AddRange(injectedWorkflow.Rules);
}
}
return workflowRules;
}
}
private static string GetCompileRulesKey(string workflowName, RuleParameter[] ruleParams)
{
return $"{workflowName}-" + String.Join("-", ruleParams.Select(c => c.Type.Name));
}
/// <summary>
/// This will execute the compiled rules
/// </summary>
/// <param name="workflowName"></param>
/// <param name="ruleParams"></param>
/// <returns>list of rule result set</returns>
private List<RuleResultTree> ExecuteRuleByWorkflow(string workflowName, RuleParameter[] ruleParams)
{
_logger.LogTrace($"Compiled rules found for {workflowName} workflow and executed");
List<RuleResultTree> result = new List<RuleResultTree>();
var compileRulesKey = GetCompileRulesKey(workflowName, ruleParams);
var inputs = ruleParams.Select(c => c.Value);
foreach (var compiledRule in (compileRulesDic[compileRulesKey] as CompiledRule).CompiledRules)
{
result.Add(compiledRule.DynamicInvoke(new List<object>(inputs) { new RuleInput() }.ToArray()) as RuleResultTree);
}
return result;
}
#endregion
}
}