// 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 messages, ConditionGroup cGroup, List failedFieldList, out string message) { bool? result = null; message = ""; bool conditionResult = false; try { // Bedingungen validieren foreach (ValidationCondition condition in cGroup.Conditions) { object[] otherargs = null; // den aktuellen Wert bestimmen if(!ValidationRule.ValidationFieldDict.ContainsKey(condition.FieldName)) { // TODO: Warnung absetzen continue; } ValidationField vf = ValidationRule.ValidationFieldDict[condition.FieldName]; // Wert aus den Daten auslesen // Dabei muss unterschieden werden: 1. skalarer Wert // 2. Wert eines Listenelements // // als Rückgabe würde dann benötigt der Name des Property, die beteiligte Klasse und natürlich der Identifier (Index) wenn es eine Listenposition war //if(vf.) //object value = vf.PropertyInfo.GetValue() switch(condition.ConditionOperator) { case ConditionOperatorEnum.EQUAL: break; case ConditionOperatorEnum.NOT_EQUAL: break; case ConditionOperatorEnum.LESS: break; case ConditionOperatorEnum.GREATER: break; case ConditionOperatorEnum.NULL: break; case ConditionOperatorEnum.NOT_NULL: break; } 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 /// /// evaluate logical group operator /// 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 #region ICloneable implementation public object Clone() { return this.MemberwiseClone(); } #endregion } #region class ConditionGroup /// /// 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ß. /// [Serializable] public class ConditionGroup { public enum GroupOperatorEnum { AND, OR, NOT, XOR } public ConditionGroup() { Conditions = new ObservableCollection(); SubGroups = new ObservableCollection(); XMLConditions = new List(); XMLSubGroups = new List(); } #region Properties [XmlIgnore] public ObservableCollection Conditions { get; set; } public List XMLConditions { get; set; } public GroupOperatorEnum GroupOperator { get; set; } public string ErrorMessage { get; set; } [XmlIgnore] public ObservableCollection SubGroups { get; set; } public List 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 /// /// Diese Klasse wird beim Bearbeiten im Regel-Editor verwendet (ENI-2) /// 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; } } 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 { public K Key { get; set; } public V Value { get; set; } } #endregion }