using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Esiur.Data; using System.Runtime.CompilerServices; using System.Reflection; using Esiur.Net.IIP; using Esiur.Misc; using Esiur.Security.Permissions; using Esiur.Resource.Template; using Esiur.Security.Authority; namespace Esiur.Resource { public class Instance { string name; AutoList children;// = new AutoList(); IResource resource; IStore store; AutoList parents;// = new AutoList(); //bool inherit; ResourceTemplate template; AutoList managers;// = new AutoList(); public delegate void ResourceModifiedEvent(IResource resource, string propertyName, object newValue); //public delegate void ResourceEventOccurredEvent(IResource resource, string eventName, string[] users, DistributedConnection[] connections, object[] args); public delegate void ResourceEventOccurredEvent(IResource resource, object issuer, Session[] receivers, string eventName, object[] args); public delegate void ResourceDestroyedEvent(IResource resource); public event ResourceModifiedEvent ResourceModified; public event ResourceEventOccurredEvent ResourceEventOccurred; public event ResourceDestroyedEvent ResourceDestroyed; KeyList attributes; List ages = new List(); List modificationDates = new List(); private ulong instanceAge; private DateTime instanceModificationDate; uint id; /// /// Instance attributes are custom properties associated with the instance, a place to store information by IStore. /// public KeyList Attributes { get { return attributes; } } public override string ToString() { return name + " (" + Link + ")"; } public bool RemoveAttributes(string[] attributes = null) { if (attributes == null) this.attributes.Clear(); else { foreach (var attr in attributes) this.attributes.Remove(attr); } return true; } public Structure GetAttributes(string[] attributes = null) { var st = new Structure(); if (attributes == null) { var clone = this.attributes.Keys.ToList(); clone.Add("managers"); attributes = clone.ToArray();// this.attributes.Keys.ToList().Add("managers"); } foreach(var attr in attributes) { if (attr == "name") st["name"] = this.name; else if (attr == "managers") { var mngrs = new List(); foreach (var manager in this.managers) mngrs.Add(new Structure() { ["type"] = manager.GetType().FullName + "," + manager.GetType().GetTypeInfo().Assembly.GetName().Name, ["settings"] = manager.Settings }); st["managers"] = mngrs.ToArray(); } else if (attr == "parents") { st["parents"] = parents.ToArray(); } else if (attr == "children") { st["children"] = children.ToArray(); } else if (attr == "childrenCount") { st["childrenCount"] = children.Count; } else if (attr == "type") { st["type"] = resource.GetType().FullName; } else st[attr] = this.attributes[attr]; } return st; } public bool SetAttributes(Structure attributes, bool clearAttributes = false) { try { if (clearAttributes) this.attributes.Clear(); foreach (var attr in attributes) if (attr.Key == "name") this.name = attr.Value as string; else if (attr.Key == "managers") { this.managers.Clear(); var mngrs = attr.Value as object[]; foreach (var mngr in mngrs) { var m = mngr as Structure; var type = Type.GetType(m["type"] as string); if (Codec.ImplementsInterface(type, typeof(IPermissionsManager))) { var settings = m["settings"] as Structure; var manager = Activator.CreateInstance(type) as IPermissionsManager; manager.Initialize(settings, this.resource); this.managers.Add(manager); } else return false; } } else { this.attributes[attr.Key] = attr.Value; } } catch { return false; } return true; } /* public Structure GetAttributes() { var st = new Structure(); foreach (var a in attributes.Keys) st[a] = attributes[a]; st["name"] = name; var mngrs = new List(); foreach (var manager in managers) { var mngr = new Structure(); mngr["settings"] = manager.Settings; mngr["type"] = manager.GetType().FullName; mngrs.Add(mngr); } st["managers"] = mngrs; return st; }*/ /// /// Get the age of a given property index. /// /// Zero-based property index. /// Age. public ulong GetAge(byte index) { if (index < ages.Count) return ages[index]; else return 0; } /// /// Set the age of a property. /// /// Zero-based property index. /// Age. public void SetAge(byte index, ulong value) { if (index < ages.Count) { ages[index] = value; if (value > instanceAge) instanceAge = value; } } /// /// Set the modification date of a property. /// /// Zero-based property index. /// Modification date. public void SetModificationDate(byte index, DateTime value) { if (index < modificationDates.Count) { modificationDates[index] = value; if (value > instanceModificationDate) instanceModificationDate = value; } } /// /// Get modification date of a specific property. /// /// Zero-based property index /// Modification date. public DateTime GetModificationDate(byte index) { if (index < modificationDates.Count) return modificationDates[index]; else return DateTime.MinValue; } /// /// Load property value (used by stores) /// /// Property name /// Property age /// Property value /// public bool LoadProperty(string name, ulong age, DateTime modificationDate, object value) { var pt = template.GetPropertyTemplate(name); if (pt == null) return false; /* #if NETSTANDARD1_5 var pi = resource.GetType().GetTypeInfo().GetProperty(name, new[] { resource.GetType() }); #else var pi = resource.GetType().GetProperty(pt.Name); #endif */ if (pt.Info.PropertyType == typeof(DistributedPropertyContext)) return false; try { if (pt.Info.CanWrite) pt.Info.SetValue(resource, DC.CastConvert(value, pt.Info.PropertyType)); } catch(Exception ex) { //Console.WriteLine(resource.ToString() + " " + name); Global.Log(ex); } SetAge(pt.Index, age); SetModificationDate(pt.Index, modificationDate); return true; } /// /// Age of the instance, incremented by 1 in every modification. /// public ulong Age { get { return instanceAge; } internal set { instanceAge = value; } } /// /// Last modification date. /// public DateTime ModificationDate { get { return instanceModificationDate; } } /// /// Instance Id. /// public uint Id { get { return id; } } /// /// Import properties from bytes array. /// /// /// public bool Deserialize(PropertyValue[] properties) { for (byte i = 0; i < properties.Length; i++) { var pt = this.template.GetPropertyTemplate(i); if (pt != null) { var pv = properties[i]; LoadProperty(pt.Name, pv.Age, pv.Date, pv.Value); } } return true; } /// /// Export all properties with ResourceProperty attributed as bytes array. /// /// public PropertyValue[] Serialize() { List props = new List(); foreach (var pt in template.Properties) { /* #if NETSTANDARD1_5 var pi = resource.GetType().GetTypeInfo().GetProperty(pt.Name); #else var pi = resource.GetType().GetProperty(pt.Name); #endif */ var rt = pt.Info.GetValue(resource, null); props.Add(new PropertyValue(rt, ages[pt.Index], modificationDates[pt.Index])); } return props.ToArray(); } /* public bool Deserialize(byte[] data, uint offset, uint length) { var props = Codec.ParseValues(data, offset, length); Deserialize(props); return true; } */ /* public byte[] Serialize(bool includeLength = false, DistributedConnection sender = null) { //var bl = new BinaryList(); List props = new List(); foreach (var pt in template.Properties) { var pi = resource.GetType().GetProperty(pt.Name); var rt = pi.GetValue(resource, null); // this is a cool hack to let the property know the sender if (rt is Func) rt = (rt as Func)(sender); props.Add(rt); } if (includeLength) { return Codec.Compose(props.ToArray(), false); } else { var rt = Codec.Compose(props.ToArray(), false); return DC.Clip(rt, 4, (uint)(rt.Length - 4)); } } public byte[] StorageSerialize() { var props = new List(); foreach(var pt in template.Properties) { if (!pt.Storable) continue; var pi = resource.GetType().GetProperty(pt.Name); if (!pi.CanWrite) continue; var rt = pi.GetValue(resource, null); props.Add(rt); } return Codec.Compose(props.ToArray(), false); } */ /// /// If True, the instance can be stored to disk. /// /// public bool IsStorable() { #if NETSTANDARD1_5 var attrs = resource.GetType().GetTypeInfo().GetCustomAttributes(typeof(Storable), true).ToArray(); #else var attrs = resource.GetType().GetCustomAttributes(typeof(Storable), true); #endif return attrs.Length > 0; } internal void EmitModification(PropertyTemplate pt, object value) { instanceAge++; var now = DateTime.UtcNow; ages[pt.Index] = instanceAge; modificationDates[pt.Index] = now; if (pt.Storage == StorageMode.NonVolatile) { store.Modify(resource, pt.Name, value, ages[pt.Index], now); } else if (pt.Storage == StorageMode.Recordable) { store.Record(resource, pt.Name, value, ages[pt.Index], now); } ResourceModified?.Invoke(resource, pt.Name, value); } /// /// Notify listeners that a property was modified. /// /// /// /// public void Modified([CallerMemberName] string propertyName = "") { object value; if (GetPropertyValue(propertyName, out value)) { var pt = template.GetPropertyTemplate(propertyName); EmitModification(pt, value); } } // internal void EmitResourceEvent(string name, string[] users, DistributedConnection[] connections, object[] args) internal void EmitResourceEvent(object issuer, Session[] receivers, string name, object[] args) { ResourceEventOccurred?.Invoke(resource, issuer, receivers, name, args); } /// /// Get the value of a given property by name. /// /// Property name /// Output value /// True, if the resource has the property. public bool GetPropertyValue(string name, out object value) { /* #if NETSTANDARD1_5 PropertyInfo pi = resource.GetType().GetTypeInfo().GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); #else PropertyInfo pi = resource.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); #endif */ var pt = template.GetPropertyTemplate(name); if (pt != null && pt.Info != null) { /* #if NETSTANDARD1_5 object[] ca = pi.GetCustomAttributes(typeof(ResourceProperty), false).ToArray(); #else object[] ca = pi.GetCustomAttributes(typeof(ResourceProperty), false); #endif if (ca.Length > 0) { value = pi.GetValue(resource, null); //if (value is Func) // value = (value as Func)(sender); return true; } */ value = pt.Info.GetValue(resource, null); return true; } value = null; return false; } /* public bool Inherit { get { return inherit; } }*/ /// /// List of parents. /// public AutoList Parents { get { return parents; } } /// /// Store responsible for creating and keeping the resource. /// public IStore Store { get { return store; } } /// /// List of children. /// public AutoList Children { get { return children; } } /// /// The unique and permanent link to the resource. /// public string Link { get { if (this.store != null) return this.store.Link(this.resource); else { var l = new List(); //l.Add(name); var p = this.resource; // parents.First(); while (true) { l.Insert(0, p.Instance.name); if (p.Instance.parents.Count == 0) break; p = p.Instance.parents.First(); } return String.Join("/", l.ToArray()); } } } /// /// Instance name. /// public string Name { get { return name; } set { name = value; } } /// /// Resource managed by this instance. /// public IResource Resource { get { return resource; } } /// /// Resource template describes the properties, functions and events of the resource. /// public ResourceTemplate Template { get { return template; } /* internal set { template = Warehouse.GetTemplate(resource.GetType()); // set ages for (byte i = 0; i < template.Properties.Length; i++) { ages.Add(0); modificationDates.Add(DateTime.MinValue); } } */ } /// /// Check for permission. /// /// Caller sessions. /// Action type /// Function, property or event to check for permission. /// Permission inquirer. /// Ruling. public Ruling Applicable(Session session, ActionType action, MemberTemplate member, object inquirer = null) { foreach (IPermissionsManager manager in managers) { var r = manager.Applicable(this.resource, session, action, member, inquirer); if (r != Ruling.DontCare) return r; } return Ruling.DontCare; } /// /// Execution managers. /// public AutoList Managers => managers; /// /// Create new instance. /// /// Instance Id. /// Name of the instance. /// Resource to manage. /// Store responsible for the resource. public Instance(uint id, string name, IResource resource, IStore store, ResourceTemplate customTemplate = null, ulong age = 0) { this.store = store; this.resource = resource; this.id = id; this.name = name; this.instanceAge = age; this.attributes = new KeyList(this); children = new AutoList(this); parents = new AutoList(this); managers = new AutoList(this); children.OnAdd += Children_OnAdd; children.OnRemoved += Children_OnRemoved; parents.OnAdd += Parents_OnAdd; parents.OnRemoved += Parents_OnRemoved; resource.OnDestroy += Resource_OnDestroy; if (customTemplate != null) this.template = customTemplate; else this.template = Warehouse.GetTemplate(resource.GetType()); // set ages for (byte i = 0; i < template.Properties.Length; i++) { ages.Add(0); modificationDates.Add(DateTime.MinValue); } // connect events Type t = resource.GetType(); #if NETSTANDARD1_5 var events = t.GetTypeInfo().GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); #else var events = t.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); #endif foreach (var evt in events) { //if (evt.EventHandlerType != typeof(ResourceEventHanlder)) // continue; if (evt.EventHandlerType == typeof(ResourceEventHanlder)) { var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true); if (ca.Length == 0) continue; ResourceEventHanlder proxyDelegate = (args) => EmitResourceEvent(null, null, evt.Name, args); evt.AddEventHandler(resource, proxyDelegate); } else if (evt.EventHandlerType == typeof(CustomResourceEventHanlder)) { var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true); if (ca.Length == 0) continue; CustomResourceEventHanlder proxyDelegate = (issuer, receivers, args) => EmitResourceEvent(issuer, receivers, evt.Name, args); evt.AddEventHandler(resource, proxyDelegate); } /* else if (evt.EventHandlerType == typeof(CustomUsersEventHanlder)) { var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true); if (ca.Length == 0) continue; CustomUsersEventHanlder proxyDelegate = (users, args) => EmitResourceEvent(evt.Name, users, null, args); evt.AddEventHandler(resource, proxyDelegate); } else if (evt.EventHandlerType == typeof(CustomConnectionsEventHanlder)) { var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true); if (ca.Length == 0) continue; CustomConnectionsEventHanlder proxyDelegate = (connections, args) => EmitResourceEvent(evt.Name, null, connections, args); evt.AddEventHandler(resource, proxyDelegate); } else if (evt.EventHandlerType == typeof(CustomReceiversEventHanlder)) { var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true); if (ca.Length == 0) continue; CustomReceiversEventHanlder proxyDelegate = (users, connections, args) => EmitResourceEvent(evt.Name, users, connections, args); evt.AddEventHandler(resource, proxyDelegate); } */ } } private void Children_OnRemoved(Instance parent, IResource value) { value.Instance.parents.Remove(resource); } private void Children_OnAdd(Instance parent, IResource value) { if (!value.Instance.parents.Contains(resource)) value.Instance.parents.Add(resource); } private void Parents_OnRemoved(Instance parent, IResource value) { value.Instance.children.Remove(resource); } private void Parents_OnAdd(Instance parent, IResource value) { if (!value.Instance.children.Contains(resource)) value.Instance.children.Add(resource); } private void Resource_OnDestroy(object sender) { ResourceDestroyed?.Invoke((IResource)sender); } } }