535 lines
18 KiB
C#
535 lines
18 KiB
C#
// Copyright (c) 2015-2017 schick Informatik
|
|
// Description: Validierungsbedingung, die (als Gruppe) als Validierungsregel serialisiert und
|
|
// ausgewertet wird
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Xml.Serialization;
|
|
using log4net;
|
|
using System.Collections.ObjectModel;
|
|
using System.Collections;
|
|
using System.Windows.Data;
|
|
using System.ComponentModel;
|
|
using System.Reflection;
|
|
|
|
namespace bsmd.database
|
|
{
|
|
[Serializable]
|
|
public class ValidationCondition : ICloneable
|
|
{
|
|
private static ILog log = LogManager.GetLogger(typeof(ValidationCondition));
|
|
|
|
public ValidationCondition() { }
|
|
|
|
public enum ConditionOperatorEnum
|
|
{
|
|
[Description("==")]
|
|
EQUAL,
|
|
[Description("!=")]
|
|
NOT_EQUAL,
|
|
[Description(">")]
|
|
GREATER,
|
|
[Description("<")]
|
|
LESS,
|
|
[Description("== NULL")]
|
|
NULL,
|
|
[Description("!= NULL")]
|
|
NOT_NULL
|
|
}
|
|
|
|
#region Properties
|
|
|
|
public string FieldName { get; set; }
|
|
|
|
public ConditionOperatorEnum ConditionOperator { get; set; }
|
|
|
|
public string Value { get; set; }
|
|
|
|
|
|
#endregion
|
|
|
|
#region static methods
|
|
|
|
public static bool? CheckConditions(MessageCore core, List<Message> messages, ConditionGroup cGroup, List<ValidationField> failedFieldList,
|
|
out string message)
|
|
{
|
|
bool? result = null;
|
|
message = "";
|
|
bool conditionResult = false;
|
|
|
|
try
|
|
{
|
|
// Bedingungen validieren
|
|
|
|
foreach (ValidationCondition condition in cGroup.Conditions)
|
|
{
|
|
object[] otherargs = null;
|
|
|
|
if(!ValidationRule.ValidationFieldDict.ContainsKey(condition.FieldName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ValidationField aFailedField = null;
|
|
bool conditionValid = condition.Evaluate(core, messages, out aFailedField);
|
|
|
|
if (!conditionValid && aFailedField != null)
|
|
failedFieldList.Add(aFailedField);
|
|
|
|
|
|
string errorMessage = cGroup.ErrorMessage;
|
|
if (otherargs != null)
|
|
errorMessage = string.Format(errorMessage, otherargs);
|
|
|
|
result = ValidationCondition.CombineConditions(result, conditionResult, cGroup.GroupOperator);
|
|
|
|
// Fehlermeldung hinzufügen falls Bedingung verletzt wurde
|
|
if (!conditionResult && ((cGroup.GroupOperator == ConditionGroup.GroupOperatorEnum.AND) || (cGroup.GroupOperator == ConditionGroup.GroupOperatorEnum.OR)))
|
|
message += string.Format("- {0}{1}", errorMessage, Environment.NewLine);
|
|
//if (conditionResult && ((cGroup.GroupOperator == ConditionGroup.GroupOperatorEnum.NAND) || (cGroup.GroupOperator == ConditionGroup.GroupOperatorEnum.NOR)))
|
|
// message += string.Format("- {0}{1}", errorMessage, Environment.NewLine);
|
|
|
|
}
|
|
|
|
// check internal groups recursively
|
|
|
|
foreach (ConditionGroup subGroup in cGroup.SubGroups)
|
|
{
|
|
string subMessage = "";
|
|
bool? subResult = ValidationCondition.CheckConditions(core, messages, subGroup, failedFieldList, out subMessage);
|
|
if (!subResult.HasValue) // an error occurred evaluating this element
|
|
{
|
|
return subResult;
|
|
}
|
|
else
|
|
{
|
|
result = ValidationCondition.CombineConditions(result, subResult.Value, cGroup.GroupOperator);
|
|
// Fehlermeldung hinzufügen falls Bedingung verletzt wurde
|
|
if (!subResult.Value && ((cGroup.GroupOperator == ConditionGroup.GroupOperatorEnum.AND) || (cGroup.GroupOperator == ConditionGroup.GroupOperatorEnum.OR)))
|
|
message += string.Format("- {0}{1}", subMessage, Environment.NewLine);
|
|
//if (subResult.Value && ((cGroup.GroupOperator == ConditionGroup.GroupOperatorEnum.NAND) || (cGroup.GroupOperator == ConditionGroup.GroupOperatorEnum.NOR)))
|
|
// message += string.Format("- {0}{1}", subMessage, Environment.NewLine);
|
|
}
|
|
}
|
|
|
|
// falls in einer "OR" Gruppe eine Bedingung fehlschlägt aber die Condition nach true evaluiert
|
|
// wird die Fehlermeldung zwecks Verwirrungsvermeidung gedroppt. Dasselbe natürl. auch für NAND
|
|
if ((cGroup.GroupOperator == ConditionGroup.GroupOperatorEnum.OR) && result.Value) message = "";
|
|
//if ((cGroup.GroupOperator == ConditionGroup.GroupOperatorEnum.NAND) && !result.Value) message = "";
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
{
|
|
message = string.Format("Validation threw exception: {0}", ex.ToString());
|
|
log.Error(message);
|
|
return false;
|
|
}
|
|
|
|
|
|
return result;
|
|
}
|
|
|
|
#region Serialization
|
|
|
|
public static ConditionGroup LoadFromString(string serializedConditions)
|
|
{
|
|
if ((serializedConditions == null) ||
|
|
(serializedConditions.Trim().Length == 0))
|
|
return null;
|
|
|
|
using (StringReader sr = new StringReader(serializedConditions))
|
|
{
|
|
try
|
|
{
|
|
XmlSerializer serializer = new XmlSerializer(typeof(ConditionGroup));
|
|
ConditionGroup container = serializer.Deserialize(sr) as ConditionGroup;
|
|
container.CopyToObservable();
|
|
return container;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static string SaveToString(ConditionGroup group)
|
|
{
|
|
if (group == null) return null;
|
|
|
|
using (StringWriter sw = new StringWriter())
|
|
{
|
|
try
|
|
{
|
|
Type theType = Type.GetType("bsmd.database.ConditionGroup, bsmd.database");
|
|
XmlSerializer serializer = new XmlSerializer(theType);
|
|
group.CopyToXML();
|
|
serializer.Serialize(sw, group);
|
|
return sw.ToString();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region field evaluation
|
|
|
|
private bool Evaluate(MessageCore core, List<Message> messages, out ValidationField failedField)
|
|
{
|
|
bool result = false;
|
|
failedField = null;
|
|
|
|
if(ValidationRule.ValidationFieldDict.ContainsKey(this.FieldName))
|
|
{
|
|
// den Wert aus der Nachrichtenklasse lesen
|
|
ValidationField aValField = ValidationRule.ValidationFieldDict[this.FieldName];
|
|
if(aValField.IsInListType)
|
|
{
|
|
List<object> vals = ValidationRule.GetValueListFromData(aValField);
|
|
|
|
|
|
// TODO: Wie melden wir hier "Listenposition 1+3 ok, aber 2 falsch" zurück?
|
|
}
|
|
else
|
|
{
|
|
object val = ValidationRule.GetValueFromData(aValField);
|
|
|
|
switch (this.ConditionOperator)
|
|
{
|
|
case ConditionOperatorEnum.EQUAL:
|
|
result = (val ?? "").ToString().Equals(this.Value);
|
|
break;
|
|
case ConditionOperatorEnum.NOT_EQUAL:
|
|
result = !(val ?? "").ToString().Equals(this.Value);
|
|
break;
|
|
case ConditionOperatorEnum.LESS:
|
|
//result = val.ToString() < this.Value;
|
|
break;
|
|
case ConditionOperatorEnum.GREATER:
|
|
|
|
break;
|
|
case ConditionOperatorEnum.NULL:
|
|
result = (val == null);
|
|
break;
|
|
case ConditionOperatorEnum.NOT_NULL:
|
|
result = (val != null);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check type and evaluate with operator (local prop)
|
|
|
|
|
|
|
|
// if failed set failedField to aValField
|
|
|
|
// else set result to true
|
|
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region hier ist das alte audius Zeugs
|
|
|
|
/// <summary>
|
|
/// evaluate logical group operator
|
|
/// </summary>
|
|
private static bool CombineConditions(bool? c1, bool c2, ConditionGroup.GroupOperatorEnum op)
|
|
{
|
|
if (!c1.HasValue) return c2;
|
|
switch (op)
|
|
{
|
|
case ConditionGroup.GroupOperatorEnum.AND:
|
|
return c1.Value & c2;
|
|
case ConditionGroup.GroupOperatorEnum.OR:
|
|
return c1.Value | c2;
|
|
case ConditionGroup.GroupOperatorEnum.NOT:
|
|
return !(c1.Value);
|
|
case ConditionGroup.GroupOperatorEnum.XOR:
|
|
return (c1.Value ^ c2);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static bool IsConditionTrue(string val1, string val2, ValidationCondition.ConditionOperatorEnum condition)
|
|
{
|
|
switch (condition)
|
|
{
|
|
case ValidationCondition.ConditionOperatorEnum.EQUAL:
|
|
return val1.Equals(val2);
|
|
default:
|
|
return !val1.Equals(val2);
|
|
}
|
|
}
|
|
|
|
private static bool IsConditionTrue(bool val1, bool val2, ValidationCondition.ConditionOperatorEnum condition)
|
|
{
|
|
switch (condition)
|
|
{
|
|
case ValidationCondition.ConditionOperatorEnum.EQUAL:
|
|
return val1.Equals(val2);
|
|
default:
|
|
return !val1.Equals(val2);
|
|
}
|
|
}
|
|
|
|
private static bool IsConditionTrue(DateTime val1, DateTime val2, ValidationCondition.ConditionOperatorEnum condition)
|
|
{
|
|
switch (condition)
|
|
{
|
|
case ValidationCondition.ConditionOperatorEnum.EQUAL:
|
|
return val1 == val2;
|
|
case ValidationCondition.ConditionOperatorEnum.GREATER:
|
|
return val1 > val2;
|
|
case ValidationCondition.ConditionOperatorEnum.LESS:
|
|
return val1 < val2;
|
|
case ValidationCondition.ConditionOperatorEnum.NOT_EQUAL:
|
|
return val1 != val2;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static bool IsConditionTrue(TimeSpan val1, TimeSpan val2, ValidationCondition.ConditionOperatorEnum condition)
|
|
{
|
|
switch (condition)
|
|
{
|
|
case ValidationCondition.ConditionOperatorEnum.EQUAL:
|
|
return val1 == val2;
|
|
case ValidationCondition.ConditionOperatorEnum.GREATER:
|
|
return val1 > val2;
|
|
case ValidationCondition.ConditionOperatorEnum.LESS:
|
|
return val1 < val2;
|
|
case ValidationCondition.ConditionOperatorEnum.NOT_EQUAL:
|
|
return val1 != val2;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static bool IsConditionTrue(double val1, double val2, ValidationCondition.ConditionOperatorEnum condition)
|
|
{
|
|
switch (condition)
|
|
{
|
|
case ValidationCondition.ConditionOperatorEnum.EQUAL:
|
|
return val1 == val2;
|
|
case ValidationCondition.ConditionOperatorEnum.GREATER:
|
|
return val1 > val2;
|
|
case ValidationCondition.ConditionOperatorEnum.LESS:
|
|
return val1 < val2;
|
|
case ValidationCondition.ConditionOperatorEnum.NOT_EQUAL:
|
|
return val1 != val2;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region ICloneable implementation
|
|
|
|
public object Clone()
|
|
{
|
|
return this.MemberwiseClone();
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
#region class ConditionGroup
|
|
|
|
/// <summary>
|
|
/// Diese Klasse benötigt (blöderweise) für eine Sache 2 Collections: einmal für die XML Serialisierung und einmal
|
|
/// für die Hierarchie im WPF Treecontrol (ObservableCollection). Daher müssen die Inhalte beim Laden und Speichern hin und herkopiert
|
|
/// werden. Was für ein Scheiß.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class ConditionGroup
|
|
{
|
|
|
|
public enum GroupOperatorEnum
|
|
{
|
|
AND,
|
|
OR,
|
|
NOT,
|
|
XOR
|
|
}
|
|
|
|
public ConditionGroup() {
|
|
Conditions = new ObservableCollection<ValidationCondition>();
|
|
SubGroups = new ObservableCollection<ConditionGroup>();
|
|
XMLConditions = new List<ValidationCondition>();
|
|
XMLSubGroups = new List<ConditionGroup>();
|
|
}
|
|
|
|
#region Properties
|
|
|
|
[XmlIgnore]
|
|
public ObservableCollection<ValidationCondition> Conditions
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public List<ValidationCondition> XMLConditions { get; set; }
|
|
|
|
public GroupOperatorEnum GroupOperator
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public string ErrorMessage { get; set; }
|
|
|
|
[XmlIgnore]
|
|
public ObservableCollection<ConditionGroup> SubGroups
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public List<ConditionGroup> XMLSubGroups { get; set; }
|
|
|
|
[XmlIgnore]
|
|
public IList Children
|
|
{
|
|
get
|
|
{
|
|
return new CompositeCollection()
|
|
{
|
|
new CollectionContainer() { Collection = SubGroups },
|
|
new CollectionContainer() { Collection = Conditions }
|
|
};
|
|
}
|
|
}
|
|
|
|
public string Name
|
|
{
|
|
get { return this.GroupOperator.ToString(); }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region public recursive methods
|
|
|
|
public void Remove(ConditionGroup group)
|
|
{
|
|
if(this.SubGroups.Contains(group))
|
|
{
|
|
this.SubGroups.Remove(group);
|
|
return;
|
|
}
|
|
// try recursive
|
|
foreach (ConditionGroup subGroup in this.SubGroups)
|
|
subGroup.Remove(group);
|
|
}
|
|
|
|
public void RemoveCondition(ValidationCondition condition)
|
|
{
|
|
if(this.Conditions.Contains(condition))
|
|
{
|
|
this.Conditions.Remove(condition);
|
|
return;
|
|
}
|
|
// try recursive
|
|
foreach (ConditionGroup subGroup in this.SubGroups)
|
|
subGroup.RemoveCondition(condition);
|
|
}
|
|
|
|
public void CopyToObservable()
|
|
{
|
|
this.SubGroups.Clear();
|
|
foreach (ConditionGroup cg in this.XMLSubGroups)
|
|
this.SubGroups.Add(cg);
|
|
this.Conditions.Clear();
|
|
foreach (ValidationCondition vc in this.XMLConditions)
|
|
this.Conditions.Add(vc);
|
|
foreach (ConditionGroup subGroup in this.XMLSubGroups)
|
|
subGroup.CopyToObservable();
|
|
}
|
|
|
|
public void CopyToXML()
|
|
{
|
|
this.XMLSubGroups.Clear();
|
|
this.XMLSubGroups.AddRange(this.SubGroups);
|
|
this.XMLConditions.Clear();
|
|
this.XMLConditions.AddRange(this.Conditions);
|
|
foreach (ConditionGroup subGroup in this.SubGroups)
|
|
subGroup.CopyToXML();
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region class ValidationField
|
|
|
|
/// <summary>
|
|
/// Diese Klasse wird beim Bearbeiten im Regel-Editor verwendet (ENI-2)
|
|
/// </summary>
|
|
public class ValidationField
|
|
{
|
|
PropertyInfo _info;
|
|
|
|
public ValidationField(PropertyInfo propertyInfo)
|
|
{
|
|
_info = propertyInfo;
|
|
}
|
|
|
|
public string PropertyName { get; set; }
|
|
|
|
public string NotificationClassText { get; set; }
|
|
|
|
public Message.NotificationClass? NotificationClass { get; set; }
|
|
|
|
public bool IsInListType { get; set; }
|
|
|
|
public PropertyInfo PropertyInfo { get { return _info; } }
|
|
|
|
/// <summary>
|
|
/// Hilfsfeld um bei Gefahrgutpositionen HAZA/HAZD auseinanderzuhalten
|
|
/// </summary>
|
|
public string ExtraInfo { get; set; }
|
|
|
|
public string FullName
|
|
{
|
|
get { return this.ToString(); }
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format("{0}.{1}", NotificationClassText, PropertyName);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region struct KeyValuePairS
|
|
|
|
[Serializable]
|
|
[XmlType(TypeName = "schickKeyValuePair")]
|
|
public struct KeyValuePairS<K, V>
|
|
{
|
|
public K Key { get; set; }
|
|
public V Value { get; set; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|