Click here to Skip to main content
15,897,113 members
Articles / General Programming / Algorithms

Jazz Up Your C# Code

Rate me:
Please Sign up or sign in to vote.
4.95/5 (11 votes)
3 Jul 2012CPOL28 min read 52K   369   54  
Maintaining code with complex permissions tends to be difficult, because the code can be distributed across multiple classes. By embedding permissions directly on methods and properties within a class, code is reduced and maintainability is simplified.
#region copyright
//<copyright>
// Copyright(C) 2012 TrackerRealm Corporation
// This file is part of the open source project - Jazz. http://jazz.codeplex.com
// 
// Jazz is open software: you can redistribute it and/or modify it 
// under the terms of the GNU Affero General Public License (AGPL) as published by 
// the Free Software Foundation, version 3 of the License.
// 
// Jazz is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty 
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
//  See the GNU Affero General Public License (AGPL) for more details.
// 
// You should have received a copy of the GNU General Public 
// License along with Jazz.  If not, see <http://www.gnu.org/licenses/>.
//
// REMOVAL OF THIS NOTICE IS VIOLATION OF THE COPYRIGHT. 
//</copyright>
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using TrackerRealm.Jazz.Common;
using C = TrackerRealm.Jazz.Common.JazzConstants;
using System.Runtime.Serialization;

namespace TrackerRealm.Jazz.Client
{
    /// <summary>
    /// Client Nexus
    /// </summary>
    public class ClientNexusBase: IDisposable
    {
        private bool isClientNexusStarted = false;
        #region Constructors
        //public class Nexus<U,P> where U: jBaseUser where P: jProfileBase
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="nexusConfig"></param>
        internal protected ClientNexusBase(NexusConfigBase nexusConfig)
        {
            this.cache = new Cache(this);
            this.Verbose = nexusConfig.Verbose;
            this.IsVoidSafetyEnhanced = nexusConfig.VoidSafetyEnhanced;
            this.RoleCheckCollection = nexusConfig.RoleCheckCollection;

            List<jObject> emptyList = new List<jObject>(16);
            List<jUserBase> userList = new List<jUserBase>(8);

            List<jProfileBase> profileList = new List<jProfileBase>(8);
            List<jProfileBase> emptyProfiles = new List<jProfileBase>(8);
            List<jWorkspaceBase> wsList = new List<jWorkspaceBase>(8);
            List<jUserBase> emptyUsers = new List<jUserBase>(8);
            List<jRoleBase> roleList = new List<jRoleBase>(8);
            List<jRoleBase> emptyRoles = new List<jRoleBase>(8);
            jWorkspaceBase publicWs = null;
            List<jWorkspaceBase> emptyWsList = new List<jWorkspaceBase>(8);
            //
            // parse the 'BindUnits' looking for specific types of 'jObject' classes
            //
            jObject[] jazzObjs = nexusConfig.BindUnits.Where(u => u is jObject).Cast<jObject>().ToArray();
            nexusConfig.BindUnits.Where(u => u is jObject).Cast<jObject>().Foreach(j => 
            {
                // need error message if not clientNexus null
                j.Flags = j.Flags.Reset(JazzFlagsType.Bound);
                if (j.IsEmpty) emptyList.Add(j);
                if (j is jUserBase) 
                {
                    userList.Add((jUserBase)j);
                    // there may be more than 1 empty jUser
                    if (j.IsEmpty) emptyUsers.Add((jUserBase)j);
                }
                else if (j is jWorkspaceBase)
                {
                    wsList.Add((jWorkspaceBase)j);
                    if (j.Name == C.WORKSPACE_PUBLIC)
                        publicWs = (jWorkspaceBase)j;
                    if (j.IsEmpty) emptyWsList.Add((jWorkspaceBase)j);  // there may be more than 1 empty jWorkspace
                }
                else if (j is jRoleBase)
                {
                    jRoleBase role = (jRoleBase)j;
                    roleList.Add(role);
                    if (j.IsEmpty) emptyRoles.Add(role);  // there may be more than 1 empty jRole
                }
                else if (j is jProfileBase)
                {
                    jProfileBase profile = (jProfileBase)j;
                    profileList.Add(profile);
                    if (j.IsEmpty) emptyProfiles.Add(profile);  // there may be more than 1 empty jProfile
                }
            });
            //
            // Bind the public workspace
            //
            emptyWsList.ForEach(ws => Bind(ws.GetType(), ws));
            if (publicWs != null)
                this._PublicWorkspace = publicWs;
            if (this._PublicWorkspace == null)
            {
                Tuple<jWorkspaceBase, jWorkspaceBase> wsTuple = nexusConfig._PublicWorkspace;
                this._PublicWorkspace = wsTuple.Item1;
                this.Bind(wsTuple.Item2.GetType(), wsTuple.Item2);
                if (wsTuple.Item2 != null)
                    this.Bind(wsTuple.Item2);  // empty Workspace
            }
            this.Bind(this._PublicWorkspace);
            //
            // Assign the Login User - choose the from either the "LoginProfile" or "BindUnits"
            //
            #region Assign the Login User
            // Basic start scenerio:
            // 1.  On first use 'BindUnits' will be empty, therefore use the 'LoginProfile',
            //     which will contain a freshly created 'Guest'.
            // 2.  Save the cache.
            // 3.  Restore the saved cached via 'BindUnits'.  
            //     This time the 'Guest' from 'BindUnits' will be used instead of the freshly created 'LoginProfile' - 'Guest'.
            string loginName = nexusConfig._LoginProfile.Name;
            if (userList.FirstOrDefault(u => u.Name == loginName) == null  &&
                (profileList.FirstOrDefault(u => u.Name == loginName) == null))
            {
                //use login property because no user or profile was found in 'BindUnits' with the same name as the 'LoginProfile' property.
                this._LoginRoles = nexusConfig._LoginProfile._Roles;
                this._LoginProfile = nexusConfig._LoginProfile;
            }
            else
            {
                // use the login profile found in the "BindUnits" that has the same name as the property 'LoginProfile'
                jUserBase userCandidate = userList.Where(u => u.Name == loginName).FirstOrDefault();
                if (userCandidate != null)
                {
                    if (userCandidate._Profile == null)
                        throw new InvalidOperationException("The login user has a null profile");
                    this._LoginProfile = userCandidate._Profile;
                }
                else
                {
                    this._LoginProfile = profileList.First(u => u.Name == loginName);
                }
                this._LoginRoles = this._LoginProfile._Roles;
            }
            if (this._LoginProfile._User == null)
                throw new InvalidOperationException("The login profile has a null user");
            #endregion
            // Bind Roles
            emptyRoles.ForEach(r => Bind(r.GetType(), r));
            roleList.ForEach(r => Bind(r));
            this.BindIEnumerable(this._LoginRoles); 
            //
            // bind Login User
            emptyUsers.Foreach(u => Bind(u.GetType(), u));
            this.Bind(this._LoginProfile._User);
            //
            // Bind Login profile
            emptyProfiles.ForEach(p => Bind(p.GetType(), p));
            //this.Bind(typeof(jProfileBase), ((jUserBase)this.LoginProfile._User.Class.EmptyInstance)._Profile);
            this.Bind(this._LoginProfile.GetType(), ((jUserBase)this._LoginProfile._User.Class.EmptyInstance)._Profile);
            this.Bind(this._LoginProfile);
            //
            // bind storage objects.
            // 
            if (emptyList.FirstOrDefault(e => e._ACL.Count() > 0) != null)
                throw new InvalidOperationException("Empty objects must have an empty ACL.");
            if (emptyList.FirstOrDefault(e => e.Workspace != this._PublicWorkspace) != null)
                throw new InvalidOperationException("Empty objects must be in the 'Public' workspace.");
            emptyList.ForEach(j => this.Bind(j.GetType(), j));
            foreach (IjStorageUnit u in nexusConfig.BindUnits)
            {
                if (!(u is jObject))
                {
                    throw new ArgumentException("Can only accept 'jObject's in the 'StorageObjects' property.");
                }
                this.Bind((jObject)u);
            }
            //// bind the login roles if they have not been bound
            //foreach (jRoleBase r in this._LoginRoles)
            //{
            //    if (!r.Flags.HasFlag(JazzFlagsType.Bound))
            //        this.Bind(r);
            //}
            this.SetObjectAccessibility();
            isClientNexusStarted = true;
        }
        #endregion
        //
        // Instance properties
        //
        #region IsVoidSafetyEnhanced
        /// <summary>
        /// <para>*** Not fully Implemented ***</para>
        /// When true empty Jazz objects are used instead of null to reduce
        /// the chances of getting 'NullReferenceException', 'ArgumentNullException', etc.
        /// </summary>
        public bool IsVoidSafetyEnhanced
        {
            get;
            private set;
        }
        #endregion
        #region Verbose
        /// <summary>
        /// 
        /// </summary>
        public VerboseType Verbose
        {
            get;
            private set;
        }
        #endregion

        #region RoleCheckCollection
        /// <summary>
        /// 
        /// </summary>
        public IEnumerable<string> RoleCheckCollection
        {
            get;
            private set;
        }
        #endregion
        #region LoginRoles
        /// <summary>
        /// 
        /// </summary>
        internal protected IEnumerable<jRoleBase> _LoginRoles
        {
            get;
            private set;
        }
        #endregion
        #region LoginProfile
        /// <summary>
        /// 
        /// </summary>
        internal protected jProfileBase _LoginProfile
        {
            get;
            private set;
        }
        #endregion
        #region Cache
        private Cache cache;
        /// <summary>
        /// A cache containing all objects that have been bound (see 'Bind' method)
        /// to the client nexus.
        /// </summary>
        public Cache Cache
        {
            get { return this.cache; }
        }
        #endregion
        #region PublicWorkspace
        /// <summary>
        /// 
        /// </summary>
        internal protected jWorkspaceBase _PublicWorkspace
        {
            get;
            private set;
        }
        #endregion
        //
        // Instance Methods
        #region Bind
        /// <summary>
        /// Binds a workflows to this client nexus. 
        /// <para>A workflow needs to be bound when first created or restored.</para>
        /// <para>Workflow may only be bound to a single nexus only once and may not be rebound to any other client nexus.</para>
        /// </summary>
        /// <param name="jazzObjects"></param>
        public void BindIEnumerable(IEnumerable<jObject> jazzObjects)
        {
            foreach (jObject wf in jazzObjects)
            {
                this.Bind(wf);
            }
        }
        /// <summary>
        /// Binds a workflows to this client nexus. 
        /// <para>A workflow needs to be bound when first created or restored.</para>
        /// <para>Workflow may only be bound to a single nexus only once and may not be rebound to any other client nexus.</para>
        /// </summary>
        /// <param name="jazzObjects"></param>
        public void Bind(params jObject[] jazzObjects)
        {
            this.BindIEnumerable(jazzObjects);
        }

        /// <summary>
        /// Binds a workflow to this client nexus. 
        /// <para>A workflow needs to be bound when first created or restored.</para>
        /// <para>Workflow may only be bound to a single nexus only once and may not be rebound to any other client nexus.</para>
        /// </summary>
        /// <param name="jazzObj"></param>
        public void Bind(jObject jazzObj)
        {
            if (jazzObj.IsBound)
            {
                if (jazzObj.ClientNexus == this) return;
                throw new ArgumentException("Bind operation - object is already bound to another client nexus.", "jazzObj");
            }
            // check if class is already bound
            jClass jC;
            if (!this.Cache.JClasses.TryGet(jazzObj.GetType(), out jC))
            {
                this.cache.JClasses.AddPlaceHolder(jazzObj.GetType(), jClass.Placeholder);
                jC = Bind(jazzObj.GetType());
            }
            if (jC == jClass.Placeholder) return; //

            jClass.jObject_JazzClassField.SetValue(jazzObj, jC);
            if (jazzObj.Workspace == null)
                jazzObj.Workspace = this._PublicWorkspace;

            jClass.jObject_ClientNexusField.SetValue(jazzObj, this);
            if (!jazzObj.Flags.HasFlag(JazzFlagsType.Initialized))
            {
                // Set Jazz.ID
                jClass.jObject_IdField.SetValue(jazzObj, jazzObj.OnAssignID());
                // set initial values
                jC.InitializeJObjectFields(jazzObj);
                jazzObj.OnInitialBind();
            }
            jazzObj.OnBind();
            if (jazzObj.IsEmpty && this._PublicWorkspace!= null && jazzObj.Workspace != this._PublicWorkspace)
                throw new InvalidOperationException(string.Format("Empty objects must be in the 'Public' workspace. {0}", jazzObj.FullName));
            jazzObj.Flags= jazzObj.Flags.Set(JazzFlagsType.Initialized |JazzFlagsType.Bound);

            if (!this.Cache.Contains(jazzObj.ID)) 
                this.Cache.Add(jazzObj);
            jazzObj.IsAccessibilitySet = false;
            jazzObj.SetAccessibility(this);
        }
        /// <summary>
        /// Create a jClass and a 'Empty' jObject for the parameter '<paramref name="jazzObjectType"/>'.
        /// </summary>
        /// <param name="jazzObjectType"></param>
        /// <returns></returns>
        public jClass Bind(Type jazzObjectType)
        {
            jClass returnClass = null;
            if (this.cache.JClasses.TryGet(jazzObjectType, out returnClass) && returnClass != jClass.Placeholder)
                return returnClass;
            returnClass = null;
            Type t = jazzObjectType;
            jObject emptyWf = null;
            MethodInfo[] ms = t.GetMethods();
            MethodInfo m = t.GetMethod("CreateEmpty", BindingFlags.Static | BindingFlags.Public, null, new Type[0], new ParameterModifier[0]);
            if (m != null)
            {
                emptyWf = (jObject)m.Invoke(null, new object[0]);
            }
            else
            {
                m = t.GetMethod("CreateEmpty", BindingFlags.Static | BindingFlags.Public, null, new Type[] { this.GetType()}, new ParameterModifier[0]);
                if (m != null)
                {
                    emptyWf = (jObject)m.Invoke(null, new object[] { this });
                    if (emptyWf.Flags.HasFlag(JazzFlagsType.Bound))
                        throw new ApplicationException("Do not 'Bind' in the 'CreateEmpty' method.");
                }
            }
            ConstructorInfo ctor = null;
            object[] ctorParams = null;
            if (emptyWf == null)
            {
                emptyWf = (jObject)FormatterServices.GetUninitializedObject(t);
                ctor = t.GetConstructor(new Type[0]);
                if (ctor != null)
                    ctorParams = new object[0];
                if (ctor == null)
                {
                    ctor = t.GetConstructor(new Type[] { typeof(string) });
                    ctorParams = new object[] { C.EMPTY };
                }
                if (ctor == null)
                {
                    ctor = t.GetConstructor(new Type[] { this.GetType() });
                    ctorParams = new object[] { this };
                }
                if (ctor == null)
                {
                    ctor = t.GetConstructor(new Type[] { this.GetType(), typeof(string) });
                    ctorParams = new object[] { this, C.EMPTY };
                }
                if (ctor == null)
                {
                    if (this.IsVoidSafetyEnhanced)
                        throw new Exception(string.Format(
                            "Unable to Create empty object for the class '{0}'.  To eliminate this error " +
                            "a) Add a 'CreateEmpty' method, or " +
                            "b) Create an Empty object separately, or " +
                            "c) Set EnhancedVoidSafety to false.",
                            t.FullName));
                }
            }
            jClass jc = Bind(t, emptyWf, ctor, ctorParams);
            if (emptyWf != null)
                Bind(emptyWf);
            jc.CrossCheck(this);
            if (returnClass == null)
                returnClass = jc;
            return returnClass;
        }
        /// <summary>
        /// Creates a 'jClass' and bind the empty jObject.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="empty">This jObject will be made into the empty object for the this 'type'.</param>
        /// <returns></returns>
        public jClass Bind(Type type, jObject empty)
        {
            return this.Bind(type, empty, null, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="empty"></param>
        /// <param name="ctor"></param>
        /// <param name="ctorParams"></param>
        /// <returns></returns>
        private jClass Bind(Type type, jObject empty, ConstructorInfo ctor, object[] ctorParams)
        {
            if (typeof(jObject).IsSubclassOf(type))
                throw new ArgumentException(string.Format("The type, '{0}', is not a sub/child class of 'jObject'.  The parameter 'type' needs to inherit from 'jObject'.", type.FullName));
            if (type != empty.GetType())
                throw new ArgumentException(string.Format("The argument 'empty' was expected to be of type '{0}', but was of type '{1}'.", type.FullName, empty.GetType().FullName));
            jClass jC;
            if (Cache.JClasses.TryGet(type, out jC))
            {
                if (jC != jClass.Placeholder)
                {
                    if (!Object.ReferenceEquals(jC.EmptyInstance, empty))
                        throw new ArgumentException(string.Format("The type, '{0}', has already been bound with a empty object '{1}'.", type.FullName, jC.EmptyInstance.FullName));
                    return jC;
                }
            }
            empty.IsEmpty = true;
            jC = new jClass(type, this);
            jC.EmptyInstance = empty;
            if (!this.Cache.Contains(jC.guid))
            {
                this.Cache.Add(jC);
            }
            if (ctor != null)
            {
                //jClass.jObject_JazzClassField.SetValue(empty, jC);
                //if (empty.Workspace == null)
                //    empty.Workspace = this._PublicWorkspace;

                //jClass.jObject_ClientNexusField.SetValue(empty, this);
                ctor.Invoke(empty, ctorParams);
            }
            
            //
            // Bind empty if not bound
            //
            if (!empty.Flags.HasFlag(JazzFlagsType.Bound))
                this.Bind(empty);
            jC.CrossCheck(this);
            #region set IsReadOnly
            empty.Flags = empty.Flags.Set(JazzFlagsType.ReadOnly);
            #endregion
            return jC;
        }
        #endregion
        #region ChangeLoginProfile
        /// <summary>
        /// Changes the login user.  This may result is different behaviour of workflows.
        /// <para>The login roles changing may cause the accessibility of properties and methods
        /// and methods to change.</para>
        /// <para>When the login user is changed then the ACL (Access Control List) of
        /// workflows may change the accessibility of workflows to read/write, read only
        /// or inaccessible.</para>
        /// </summary>
        /// <param name="loginUser"></param>
        public LoggedinUser ChangeLoginProfile(jProfileBase loginUser)
        {
            this._LoginRoles = loginUser._Roles;
            this._LoginProfile = loginUser._Profile;
            this.BindIEnumerable(this._LoginRoles);
            this.Bind(loginUser._User);
            this.BindIEnumerable(loginUser._User._Profiles);
            this.SetObjectAccessibility();
            return new LoggedinUser(loginUser, (ClientNexusBase)this);
        }
        #endregion
        #region SetObjectAccessibility
        /// <summary>
        /// 
        /// </summary>
        private void SetObjectAccessibility()
        {
            this.cache.OfType<jObject>().Foreach(j => j.IsAccessibilitySet = false);
            this.cache.OfType<jObject>().Foreach(j => j.SetAccessibility(this));
        }
        #endregion
        #region IDisposable Members
        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            //remove connection with nexus
        }

        #endregion
        #region Experimental Code
        /// <summary>
        /// This is an unsupported method.  In future versions it may be removed or cease to operate.
        /// </summary>
        /// <param name="jazzObj"></param>
        protected void UnBind(jObject jazzObj)
        {
            if (!jazzObj.Flags.HasFlag(JazzFlagsType.Initialized))
                throw new ArgumentException("Jazz object needs to be properly initialized.  Bind to nexus first.");
            if (!jazzObj.Flags.HasFlag(JazzFlagsType.Bound))
                throw new ArgumentException("Jazz object is not bound.");
            jazzObj.Flags = jazzObj.Flags.Reset(JazzFlagsType.Bound);
        }
        /// <summary>
        /// This is an unsupported method.  In future versions it may be removed or cease to operate.
        /// </summary>
        /// <param name="jazzObj"></param>
        protected void ReBind(jObject jazzObj)
        {
            if (!jazzObj.Flags.HasFlag(JazzFlagsType.Initialized))
                throw new ArgumentException("Jazz object needs to be properly initialized.  Bind to nexus first.");
            jazzObj.Flags = jazzObj.Flags.Set(JazzFlagsType.Bound);
        }
        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer TrackerRealm
Canada Canada
Charles Wiebe and John Hansen are Microsoft .NET software architect/designer, for Windows Forms and ASP.NET solutions.
Charles specializes in the application layer – Web Parts and other ways of building GUI’s. John specializes in high capacity object oriented systems and connectivity.

John and Charles are co-developers of Jetfire – a .net open source, multi-user, application domain specific language (DSL) with syntax heavily based on C# and Java. The goal of Jetfire is to allow power users to quickly and easily develop and deploy applications, in much the same way as Excel allows powers users to quickly develop spread sheets.

Their latest project is Jazz - a compact, modular framework that allows new, or existing, applications to easily employ roles, states, ACLs and void safety. Jazz allows complete workflows to be implemented in only a few hundred lines of C# or VB code.

Comments and Discussions