// 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; 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 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 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 /// /// 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 #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; } } /// /// Hilfsfeld um bei Gefahrgutpositionen HAZA/HAZD auseinanderzuhalten /// 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 { public K Key { get; set; } public V Value { get; set; } } #endregion }