378 lines
15 KiB
C#
378 lines
15 KiB
C#
//
|
|
// Class: RuleEngine
|
|
// Current CLR: 4.0.30319.34209
|
|
// System: Microsoft Visual Studio 10.0
|
|
// Author: dani
|
|
// Created: 9/7/2015 8:16:42 AM
|
|
//
|
|
// Copyright (c) 2015 Informatikbüro Daniel Schick. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Text.RegularExpressions;
|
|
using log4net;
|
|
|
|
namespace bsmd.database
|
|
{
|
|
public class RuleEngine
|
|
{
|
|
|
|
#region german LOCODE's static definition
|
|
|
|
private static List<string> gerLocodeList = new List<string>() {
|
|
"DE001",
|
|
"DEAND",
|
|
"DEBDF",
|
|
"DEBZR",
|
|
"DEBMK",
|
|
"DEBKE",
|
|
"DEBRE",
|
|
"DEBRV",
|
|
"DEBRB",
|
|
"DE651",
|
|
"DEBUM",
|
|
"DEBUZ",
|
|
"DECUX",
|
|
"DEDMG",
|
|
"DEDTM",
|
|
"DEDUI",
|
|
"DEDUS",
|
|
"DEECK",
|
|
"DEELS",
|
|
"DEEME",
|
|
"DEEMM",
|
|
"DEFLF",
|
|
"DEGEK",
|
|
"DEGLU",
|
|
"DEGRD",
|
|
"DEHAM",
|
|
"DEHHF",
|
|
"DEHGL",
|
|
"DEHUS",
|
|
"DEITZ",
|
|
"DEKEL",
|
|
"DE136",
|
|
"DEKCH",
|
|
"DEKLE",
|
|
"DECGN",
|
|
"DEKRE",
|
|
"DE241",
|
|
"DELEE",
|
|
"DELEW",
|
|
"DELIS",
|
|
"DELBC",
|
|
"DELBM",
|
|
"DE002",
|
|
"DEMHG",
|
|
"DEMOZ",
|
|
"DEMUK",
|
|
"DEMUH",
|
|
"DENSS",
|
|
"DENHO",
|
|
"DENHA",
|
|
"DEOLO",
|
|
"DETRD",
|
|
"DEPAP",
|
|
"DEPEF",
|
|
"DEPUT",
|
|
"DEREC",
|
|
"DEREN",
|
|
"DERHB",
|
|
"DERSK",
|
|
"DESAS",
|
|
"DESTL",
|
|
"DEUCK",
|
|
"DE585",
|
|
"DEVRD",
|
|
"DEWED",
|
|
"DEWVN",
|
|
"DEWIS",
|
|
"DE003",
|
|
"DEWOL",
|
|
"DEWYK"
|
|
};
|
|
|
|
#endregion
|
|
|
|
private static ILog log = LogManager.GetLogger(typeof(RuleEngine));
|
|
private static Dictionary<int, string> errorTextList = null;
|
|
private static Dictionary<int, string> violationTextList = null;
|
|
|
|
private Dictionary<DatabaseEntity, List<MessageError>> errorDict = new Dictionary<DatabaseEntity, List<MessageError>>();
|
|
private Dictionary<DatabaseEntity, List<MessageViolation>> violationDict = new Dictionary<DatabaseEntity, List<MessageViolation>>();
|
|
|
|
public RuleEngine()
|
|
{
|
|
if (RuleEngine.errorTextList == null)
|
|
RuleEngine.errorTextList = DBManager.Instance.LoadErrorTexts();
|
|
if (RuleEngine.violationTextList == null)
|
|
RuleEngine.violationTextList = DBManager.Instance.LoadViolationTexts();
|
|
}
|
|
|
|
public Dictionary<DatabaseEntity, List<MessageError>> ErrorDict { get { return this.errorDict; } }
|
|
public Dictionary<DatabaseEntity, List<MessageViolation>> ViolationDict { get { return this.violationDict; } }
|
|
|
|
#region public static property validation
|
|
|
|
/// <summary>
|
|
/// Test function checks decorated properties on an entity for errors (only errors, violations cannot
|
|
/// happen here)
|
|
/// </summary>
|
|
/// <param name="entity"></param>
|
|
/// <param name="errors"></param>
|
|
public static void ValidateProperties(DatabaseEntity entity, List<MessageError> errors)
|
|
{
|
|
|
|
Type objType = entity.GetType();
|
|
|
|
Type attribType = typeof(Validation1Attribute);
|
|
if (entity.GetValidationBlock() == DatabaseEntity.ValidationBlock.BLOCK2)
|
|
attribType = typeof(Validation2Attribute);
|
|
|
|
List<PropertyInfo> props = new List<PropertyInfo>();
|
|
|
|
// add "generic" validation properties to check list
|
|
props.AddRange(objType.GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(ValidationAttribute))));
|
|
// add "block" validation properties to check list
|
|
props.AddRange(objType.GetProperties().Where( prop => Attribute.IsDefined(prop, attribType)) );
|
|
|
|
foreach (PropertyInfo property in props)
|
|
{
|
|
object propValue = property.GetValue(entity, null);
|
|
string value = (propValue == null) ? string.Empty : propValue.ToString();
|
|
ValidationCode validationCode = ValidationCode.NONE;
|
|
int maxlen = 0;
|
|
|
|
if (Attribute.IsDefined(property, attribType))
|
|
{
|
|
if (entity.GetValidationBlock() == DatabaseEntity.ValidationBlock.BLOCK1)
|
|
{
|
|
Validation1Attribute validationAttribute = Attribute.GetCustomAttribute(property, typeof(Validation1Attribute))
|
|
as Validation1Attribute;
|
|
validationCode = validationAttribute.Code;
|
|
}
|
|
else
|
|
{
|
|
Validation2Attribute validationAttribute = Attribute.GetCustomAttribute(property, typeof(Validation2Attribute))
|
|
as Validation2Attribute;
|
|
validationCode = validationAttribute.Code;
|
|
}
|
|
}
|
|
if (validationCode == ValidationCode.NONE)
|
|
{
|
|
if (Attribute.IsDefined(property, typeof(ValidationAttribute)))
|
|
{
|
|
ValidationAttribute validationAttribute = Attribute.GetCustomAttribute(property, typeof(ValidationAttribute))
|
|
as ValidationAttribute;
|
|
validationCode = validationAttribute.Code;
|
|
maxlen = validationAttribute.MaxLen;
|
|
}
|
|
}
|
|
|
|
// check properties
|
|
|
|
switch (validationCode)
|
|
{
|
|
case ValidationCode.NOT_NULL:
|
|
if (value.Length == 0) errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
break;
|
|
case ValidationCode.LOCODE:
|
|
{
|
|
Regex rgx = new Regex("[A-Z]{2}[A-Z0-9]{3}");
|
|
if (!rgx.IsMatch(value)) errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
break;
|
|
case ValidationCode.LOCODE_GER:
|
|
{
|
|
if(!RuleEngine.gerLocodeList.Contains(value))
|
|
errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
break;
|
|
case ValidationCode.INT_GT_ZERO:
|
|
{
|
|
int intVal = 0;
|
|
if (!Int32.TryParse(value, out intVal) || intVal <= 0)
|
|
errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
break;
|
|
case ValidationCode.DOUBLE_GT_ZERO:
|
|
{
|
|
double dVal = 0;
|
|
if (!Double.TryParse(value, out dVal) || dVal <= 0)
|
|
errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
break;
|
|
case ValidationCode.GISIS:
|
|
{
|
|
Regex rgx = new Regex("[0-9]{4}");
|
|
if (!rgx.IsMatch(value)) errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
break;
|
|
case ValidationCode.FLAG_CODE:
|
|
{
|
|
Regex rgx = new Regex("[A-Z]{2}");
|
|
if(!rgx.IsMatch(value)) errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
break;
|
|
case ValidationCode.TWO_DIGIT:
|
|
{
|
|
Regex rgx = new Regex("[0-9]{2}");
|
|
if (!rgx.IsMatch(value)) errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
break;
|
|
case ValidationCode.STRING_EXACT_LEN:
|
|
{
|
|
if (value.Length != maxlen)
|
|
errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
break;
|
|
case ValidationCode.STRING_MAXLEN:
|
|
{
|
|
if (value.Length > maxlen)
|
|
errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
break;
|
|
case ValidationCode.STRING_IMOCLASS:
|
|
{
|
|
Regex rgx = new Regex(@"[1-9]{1}(\.[1-9]{1})?");
|
|
if (!rgx.IsMatch(value)) errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
break;
|
|
case ValidationCode.STRING_UNNUMBER:
|
|
{
|
|
Regex rgx = new Regex("[0-9]{4}");
|
|
if (!rgx.IsMatch(value)) errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
break;
|
|
case ValidationCode.DRAUGHT_IMPLAUSIBLE:
|
|
{
|
|
double dVal = 0;
|
|
if (!Double.TryParse(value, out dVal) || dVal <= 0)
|
|
errors.Add(RuleEngine.CreateError(ValidationCode.DOUBLE_GT_ZERO, property.Name, value));
|
|
else
|
|
if ((dVal < 20) || (dVal > 150))
|
|
errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
break;
|
|
case ValidationCode.TIME_IMPLAUSIBLE:
|
|
{
|
|
DateTime aTime;
|
|
if (value.Length == 0) errors.Add(RuleEngine.CreateError(ValidationCode.NOT_NULL, property.Name, value));
|
|
if (DateTime.TryParse(value, out aTime))
|
|
{
|
|
if ((aTime - DateTime.UtcNow).Minutes > 30)
|
|
errors.Add(RuleEngine.CreateError(validationCode, property.Name, value));
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region public methods
|
|
|
|
/// <summary>
|
|
/// Diese Funktion wird für Nachrichtenklassen (MDH, SEC,.. usw.) aufgerufen. Error in eingebetteten
|
|
/// Klassen werden dann der Nachrichtenklasse zugeordnet (können dann logischerweise mehrfach auftreten)
|
|
/// </summary>
|
|
public void Validate(DatabaseEntity entity)
|
|
{
|
|
if (!(entity is Message)) return;
|
|
|
|
List<MessageError> errors = new List<MessageError>();
|
|
List<MessageViolation> violations = new List<MessageViolation>();
|
|
this.errorDict[entity] = errors;
|
|
this.violationDict[entity] = violations;
|
|
|
|
foreach (DatabaseEntity derivedEntity in ((Message)entity).Elements)
|
|
{
|
|
// individuelle Fehler nach Nachrichtenklasse prüfen
|
|
derivedEntity.MessageCore = entity.MessageCore; // some instance we need info from core (NOA / Transit)
|
|
RuleEngine.ValidateProperties(derivedEntity, errors);
|
|
derivedEntity.Validate(errors, violations);
|
|
|
|
}
|
|
|
|
foreach (MessageError error in errors)
|
|
{
|
|
error.MessageHeaderId = entity.Id.Value;
|
|
DBManager.Instance.Save(error);
|
|
}
|
|
|
|
foreach (MessageViolation violation in violations)
|
|
{
|
|
violation.MessageHeaderId = entity.Id.Value;
|
|
DBManager.Instance.Save(violation);
|
|
}
|
|
|
|
log.InfoFormat("Msg Id {0} Type {1} has {2} errors and {3} violations",
|
|
entity.Id, entity.MessageNotificationClass, errors.Count, violations.Count);
|
|
|
|
if (errors.Count > 0)
|
|
{
|
|
((Message)entity).InternalStatus = Message.BSMDStatus.ERROR;
|
|
DBManager.Instance.Save(entity);
|
|
}
|
|
else if (violations.Count > 0)
|
|
{
|
|
((Message)entity).InternalStatus = Message.BSMDStatus.VIOLATION;
|
|
DBManager.Instance.Save(entity);
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region private helper
|
|
|
|
internal static MessageError CreateError(ValidationCode validationCode, string p, string value)
|
|
{
|
|
|
|
MessageError error = new MessageError();
|
|
|
|
error.ErrorCode = (int)validationCode;
|
|
if (errorTextList.ContainsKey((int)validationCode))
|
|
{
|
|
error.ErrorText = string.Format(errorTextList[(int)validationCode], p, value);
|
|
}
|
|
else
|
|
{
|
|
error.ErrorText = p;
|
|
if (value != null)
|
|
error.ErrorText += " - " + value;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
internal static MessageViolation CreateViolation(ValidationCode validationCode, string p, string value)
|
|
{
|
|
|
|
MessageViolation violation = new MessageViolation();
|
|
|
|
violation.ViolationCode = (int)validationCode;
|
|
if (violationTextList.ContainsKey((int)validationCode))
|
|
{
|
|
violation.ViolationText = string.Format(violationTextList[(int)validationCode], p, value);
|
|
}
|
|
else
|
|
{
|
|
violation.ViolationText = p;
|
|
if (value != null)
|
|
violation.ViolationText += " - " + value;
|
|
}
|
|
return violation;
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|