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; using Esiur.Proxy; using Esiur.Core; using System.Text.Json; using System.ComponentModel.DataAnnotations.Schema; namespace Esiur.Resource { [NotMapped] public class Instance { string name; // public int IntVal { get; set; } WeakReference resource; IStore store; ResourceTemplate template; AutoList managers; public delegate void ResourceModifiedEvent(IResource resource, string propertyName, object newValue); public delegate void ResourceEventOccurredEvent(IResource resource, string eventName, object args); public delegate void CustomResourceEventOccurredEvent(IResource resource, object issuer, Func receivers, string eventName, object args); public delegate void ResourceDestroyedEvent(IResource resource); public event ResourceModifiedEvent ResourceModified; public event ResourceEventOccurredEvent ResourceEventOccurred; public event CustomResourceEventOccurredEvent CustomResourceEventOccurred; public event ResourceDestroyedEvent ResourceDestroyed; bool loading = false; //KeyList attributes; List ages = new List(); List modificationDates = new List(); private ulong instanceAge; private DateTime instanceModificationDate; uint id; public KeyList Variables { get; } = new KeyList(); /// /// 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) { return false; /* IResource res; if (!resource.TryGetTarget(out res)) return false; return store.RemoveAttributes(res, attributes); */ /* if (attributes == null) this.attributes.Clear(); else { foreach (var attr in attributes) this.attributes.Remove(attr); } return true; */ } public Structure GetAttributes(string[] attributes = null) { // @TODO Structure rt = new Structure(); if (attributes != null) { for (var i = 0; i < attributes.Length; i++) { var at = template.GetAttributeTemplate(attributes[i]); if (at != null) { } } } return rt; /* 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) { // @ TODO IResource res; if (resource.TryGetTarget(out res)) { foreach (var kv in attributes) { var at = template.GetAttributeTemplate(kv.Key); if (at != null) if (at.Info.CanWrite) at.Info.SetValue(res, DC.CastConvert(kv.Value, at.Info.PropertyType)); } } return true; /* 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; IResource res; if (this.resource.TryGetTarget(out res)) { manager.Initialize(settings, res); 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) { IResource res; if (!resource.TryGetTarget(out res)) return false; var pt = template.GetPropertyTemplateByName(name); if (pt == null) return false; /* #if NETSTANDARD 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; if (pt.Info.CanWrite) { try { loading = true; pt.Info.SetValue(res, DC.CastConvert(value, pt.Info.PropertyType)); } catch (Exception ex) { //Console.WriteLine(resource.ToString() + " " + name); Global.Log(ex); } loading = false; } 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.GetPropertyTemplateByIndex(i); if (pt != null) { var pv = properties[i]; LoadProperty(pt.Name, pv.Age, pv.Date, pv.Value); } } return true; } public string ToJson() { IResource res; if (resource.TryGetTarget(out res)) return JsonSerializer.Serialize(res, Global.SerializeOptions); else return null; } /// /// Export all properties with ResourceProperty attributed as bytes array. /// /// public PropertyValue[] Serialize() { List props = new List(); foreach (var pt in template.Properties) { IResource res; if (resource.TryGetTarget(out res)) { var rt = pt.Info.GetValue(res, 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 NETSTANDARD 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) { IResource res; if (this.resource.TryGetTarget(out res)) { instanceAge++; var now = DateTime.UtcNow; ages[pt.Index] = instanceAge; modificationDates[pt.Index] = now; if (pt.Recordable) { store.Record(res, pt.Name, value, ages[pt.Index], now); } else //if (pt.Storage == StorageMode.Recordable) { store.Modify(res, pt.Name, value, ages[pt.Index], now); } ResourceModified?.Invoke(res, pt.Name, value); } } /// /// Notify listeners that a property was modified. /// /// /// /// public void Modified([CallerMemberName] string propertyName = "") { if (loading) return; object value; if (GetPropertyValue(propertyName, out value)) { var pt = template.GetPropertyTemplateByName(propertyName); EmitModification(pt, value); } } // internal void EmitResourceEvent(string name, string[] users, DistributedConnection[] connections, object[] args) internal void EmitCustomResourceEvent(object issuer, Func receivers, string name, object args) { IResource res; if (this.resource.TryGetTarget(out res)) { CustomResourceEventOccurred?.Invoke(res, issuer, receivers, name, args); } } internal void EmitResourceEvent(string name, object args) { IResource res; if (this.resource.TryGetTarget(out res)) { ResourceEventOccurred?.Invoke(res, 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 NETSTANDARD 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.GetPropertyTemplateByName(name); if (pt != null && pt.Info != null) { /* #if NETSTANDARD 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; } */ IResource res; if (resource.TryGetTarget(out res)) value = pt.Info.GetValue(res, null); else { value = null; return false; } return true; } value = null; return false; } /* public bool Inherit { get { return inherit; } }*/ /// /// List of parents. /// //public AutoList Parents => parents; /// /// Store responsible for creating and keeping the resource. /// public IStore Store { get { return store; } } /// /// List of children. /// // public AutoList Children => children; /// /// The unique and permanent link to the resource. /// public string Link { get { IResource res; if (this.resource.TryGetTarget(out res)) { if (res == res.Instance.store) return name; // root store else return store.Link(res); } else return null; } } public AsyncBag Children(string name = null) where T : IResource { IResource res; if (this.resource.TryGetTarget(out res)) { //if (!(store is null)) return store.Children(res, name); //else // return (res as IStore).Children(res, name); } else return new AsyncBag(null); } public AsyncBag Parents(string name = null) where T : IResource { IResource res; if (this.resource.TryGetTarget(out res)) { return store.Parents(res, name); } else return new AsyncBag(null); } /* { 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 { IResource res; if (this.resource.TryGetTarget(out res)) { return res; } else return null; } } /// /// 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) { IResource res; if (this.resource.TryGetTarget(out res)) { //return store.Applicable(res, session, action, member, inquirer); foreach (IPermissionsManager manager in managers) { var r = manager.Applicable(res, 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 = new WeakReference(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 = ResourceProxy.GetBaseType(resource); #if NETSTANDARD 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 template.Events) { //if (evt.EventHandlerType != typeof(ResourceEventHanlder)) // continue; if (evt.Info == null) continue; if (evt.Info.EventHandlerType == typeof(ResourceEventHanlder)) { // var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true); // if (ca.Length == 0) // continue; ResourceEventHanlder proxyDelegate = (args) => EmitResourceEvent(evt.Name, args); evt.Info.AddEventHandler(resource, proxyDelegate); } else if (evt.Info.EventHandlerType == typeof(CustomResourceEventHanlder)) { //var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true); //if (ca.Length == 0) // continue; CustomResourceEventHanlder proxyDelegate = (issuer, receivers, args) => EmitCustomResourceEvent(issuer, receivers, evt.Name, args); evt.Info.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); } */ } } //IQueryable Children => store.GetChildren(this); /* * 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); } } }