File: Rules\RuleSet.cs
Project: ndp\cdf\src\WF\Activities\System.Workflow.Activities.csproj (System.Workflow.Activities)
// ---------------------------------------------------------------------------
// Copyright (C) 2005 Microsoft Corporation All Rights Reserved
// ---------------------------------------------------------------------------
 
using System.Collections.Generic;
using System.ComponentModel;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.Activities.Common;
 
namespace System.Workflow.Activities.Rules
{
    public enum RuleChainingBehavior
    {
        None,
        UpdateOnly,
        Full
    };
 
    [Serializable]
    public class RuleSet
    {
        internal const string RuleSetTrackingKey = "RuleSet.";
        internal string name;
        internal string description;
        internal List<Rule> rules;
        internal RuleChainingBehavior behavior = RuleChainingBehavior.Full;
        private bool runtimeInitialized;
        private object syncLock = new object();
 
        // keep track of cached data
        [NonSerialized]
        private RuleEngine cachedEngine;
        [NonSerialized]
        private RuleValidation cachedValidation;
 
        public RuleSet()
        {
            this.rules = new List<Rule>();
        }
 
        public RuleSet(string name)
            : this()
        {
            this.name = name;
        }
 
        public RuleSet(string name, string description)
            : this(name)
        {
            this.description = description;
        }
 
        public string Name
        {
            get { return name; }
            set
            {
                if (runtimeInitialized)
                    throw new InvalidOperationException(SR.GetString(SR.Error_CanNotChangeAtRuntime));
                name = value;
            }
        }
 
        public string Description
        {
            get { return description; }
            set
            {
                if (runtimeInitialized)
                    throw new InvalidOperationException(SR.GetString(SR.Error_CanNotChangeAtRuntime));
                description = value;
            }
        }
 
        public RuleChainingBehavior ChainingBehavior
        {
            get { return behavior; }
            set
            {
                if (runtimeInitialized)
                    throw new InvalidOperationException(SR.GetString(SR.Error_CanNotChangeAtRuntime));
                behavior = value;
            }
        }
 
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public ICollection<Rule> Rules
        {
            get { return rules; }
        }
 
        public bool Validate(RuleValidation validation)
        {
            if (validation == null)
                throw new ArgumentNullException("validation");
 
            // Validate each rule.
            Dictionary<string, object> ruleNames = new Dictionary<string, object>();
            foreach (Rule r in rules)
            {
                if (!string.IsNullOrEmpty(r.Name))  // invalid names caught when validating the rule
                {
                    if (ruleNames.ContainsKey(r.Name))
                    {
                        // Duplicate rule name found.
                        ValidationError error = new ValidationError(Messages.Error_DuplicateRuleName, ErrorNumbers.Error_DuplicateConditions);
                        error.UserData[RuleUserDataKeys.ErrorObject] = r;
                        validation.AddError(error);
                    }
                    else
                    {
                        ruleNames.Add(r.Name, null);
                    }
                }
 
                r.Validate(validation);
            }
 
            if (validation.Errors == null || validation.Errors.Count == 0)
                return true;
 
            return false;
        }
 
        public void Execute(RuleExecution ruleExecution)
        {
            // we have no way of knowing if the ruleset has been changed, so no caching done
            if (ruleExecution == null)
                throw new ArgumentNullException("ruleExecution");
            if (ruleExecution.Validation == null)
                throw new ArgumentException(SR.GetString(SR.Error_MissingValidationProperty), "ruleExecution");
 
            RuleEngine engine = new RuleEngine(this, ruleExecution.Validation, ruleExecution.ActivityExecutionContext);
            engine.Execute(ruleExecution);
        }
 
        internal void Execute(Activity activity, ActivityExecutionContext executionContext)
        {
            // this can be called from multiple threads if multiple workflows are 
            // running at the same time (only a single workflow is single-threaded)
            // we want to only lock around the validation and preprocessing, so that
            // execution can run in parallel.
 
            if (activity == null)
                throw new ArgumentNullException("activity");
 
            Type activityType = activity.GetType();
            RuleEngine engine = null;
            lock (syncLock)
            {
                // do we have something useable cached?
                if ((cachedEngine == null) || (cachedValidation == null) || (cachedValidation.ThisType != activityType))
                {
                    // no cache (or its invalid)
                    RuleValidation validation = new RuleValidation(activityType, null);
                    engine = new RuleEngine(this, validation, executionContext);
                    cachedValidation = validation;
                    cachedEngine = engine;
                }
                else
                {
                    // this will happen if the ruleset has already been processed
                    // we can simply use the previously processed engine
                    engine = cachedEngine;
                }
            }
 
            // when we get here, we have a local RuleEngine all ready to go
            // we are outside the lock, so these can run in parallel
            engine.Execute(activity, executionContext);
        }
 
        public RuleSet Clone()
        {
            RuleSet newRuleSet = (RuleSet)this.MemberwiseClone();
            newRuleSet.runtimeInitialized = false;
 
            if (this.rules != null)
            {
                newRuleSet.rules = new List<Rule>();
                foreach (Rule r in this.rules)
                    newRuleSet.rules.Add(r.Clone());
            }
 
            return newRuleSet;
        }
 
        public override bool Equals(object obj)
        {
            RuleSet other = obj as RuleSet;
            if (other == null)
                return false;
            if ((this.Name != other.Name)
                || (this.Description != other.Description)
                || (this.ChainingBehavior != other.ChainingBehavior)
                || (this.Rules.Count != other.Rules.Count))
                return false;
            // look similar, compare each rule
            for (int i = 0; i < this.rules.Count; ++i)
            {
                if (!this.rules[i].Equals(other.rules[i]))
                    return false;
            }
            return true;
        }
 
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
 
        internal void OnRuntimeInitialized()
        {
            lock (syncLock)
            {
                if (runtimeInitialized)
                    return;
 
                foreach (Rule rule in rules)
                {
                    rule.OnRuntimeInitialized();
                }
                runtimeInitialized = true;
            }
        }
    }
}