/// <summary> /// Decollates changes to content object which should be redirected to other records used as property sources /// </summary> /// <param name="path">path of content record</param> /// <param name="data">content object</param> /// <returns>JObject build from content object</returns> protected virtual JObject SetRelated(string path, object data) { // Establish the records to fetch and fetch them Type contentType = data.GetType(); var rpsAttributes = contentType .GetCustomAttributes(typeof(RedirectPropertySourceAttribute), false) .Cast <RedirectPropertySourceAttribute>() .Where(rpsa => !rpsa.ReadOnly) .ToList(); List <string> paths = rpsAttributes .Select(a => PathFunctions.Redirect(path, a.SourceDescriptor)) .Distinct() .ToList(); List <PropertyStore> records = Repository.Instance.Get(contentType, paths).ToList(); // Update the fetched referenced records with updated referenced properties on the content object JObject jObjectContent = JObject.FromObject(data); List <object> ids = new List <object>(); List <PropertyStore> vals = new List <PropertyStore>(); if (rpsAttributes.Any()) { foreach (var rpsAttribute in rpsAttributes) { string refdPath = PathFunctions.Redirect(path, rpsAttribute.SourceDescriptor); if (refdPath == path) // redirected to itself { continue; } PropertyStore refdRecord = records.FirstOrDefault(ps => (string)ps["Path"] == refdPath); if (refdRecord == null) { refdRecord = GetNewRecord(contentType, refdPath); } foreach (string propertyPath in rpsAttribute.PropertyPaths) { JObject refdObject = JObject.Parse((string)refdRecord["Content"]); var toFromPaths = GetPaths(propertyPath); refdObject.CopyPropertyFrom(toFromPaths[1], jObjectContent, toFromPaths[0]); refdRecord["Content"] = refdObject.ToString(); } if (!ids.Contains(refdPath)) { ids.Add(refdPath); vals.Add(refdRecord); } } if (vals.Count > 0) { Repository.Instance.Set(contentType, vals, ids); } } return(jObjectContent); }
/// <summary> /// Efficiently gets from database and collates all data referenced by property source redirects /// </summary> /// <param name="primaryPath">path of main item</param> /// <param name="primaryRecord">data record of main item, if already available, or else null</param> /// <param name="contentType">type of main content object</param> /// <returns>content object</returns> protected virtual object GetWithRelated(string primaryPath, PropertyStore primaryRecord, Type contentType) { // Establish the records to fetch and fetch them var rpsAttributes = contentType .GetCustomAttributes(typeof(RedirectPropertySourceAttribute), false) .Cast <RedirectPropertySourceAttribute>() .ToList(); List <string> paths = new List <string>(); if (primaryRecord == null) { paths.Add(primaryPath); } paths.AddRange(rpsAttributes .Select(a => PathFunctions.Redirect(primaryPath, a.SourceDescriptor)) .Distinct()); List <PropertyStore> records = Repository.Instance.Get(contentType, paths).ToList(); // Update the primary record with redirected properties from referenced records if (primaryRecord == null) { primaryRecord = records.FirstOrDefault(ps => ((string)ps["Path"]) == primaryPath); } if (primaryRecord == null) { return(null); } JObject jContent = null; if (rpsAttributes.Any()) { jContent = JObject.Parse((string)primaryRecord["Content"]); foreach (var rpsAttribute in rpsAttributes) { string refdPath = PathFunctions.Redirect(primaryPath, rpsAttribute.SourceDescriptor); if (refdPath == primaryPath) // redirected to itself { continue; } PropertyStore refdRecord = records.FirstOrDefault(ps => (string)ps["Path"] == refdPath); if (refdRecord != null) { foreach (string propertyPath in rpsAttribute.PropertyPaths) { var toFromPaths = GetPaths(propertyPath); JObject refdObject = JObject.Parse((string)refdRecord["Content"]); jContent.CopyPropertyFrom(toFromPaths[0], refdObject, toFromPaths[1]); } } } } bool isBaseContent = typeof(BaseContent).IsAssignableFrom(contentType); if (jContent == null) { return(GetContent(isBaseContent, contentType, primaryRecord)); } object content = jContent.ToObject(contentType); //if (isBaseContent) // (content as BaseContent).OriginalRecord = primaryRecord; return(content); }
/// <summary> /// Starting from a list of addresses and optionally (or only) the containers at those addresses, fetch /// any containers necessary and any other containers required to supply redirected properties for them, /// obtain the contained content items and collate their properties, returning the content items at the /// addresses. /// </summary> /// <typeparam name="T">Type of content items to return</typeparam> /// <param name="startContainers">Initial list of containers if they are available</param> /// <param name="startAddresses">Initial list of addresses, which may be omitted and derived from containers</param> /// <returns>List of content items</returns> public IEnumerable <T> Collate <T>(IEnumerable <object> startContainers, IEnumerable <Address> startAddresses) where T : class { // place to store all the containers we have currently Dictionary <VersionedAddress, object> containers; ItemVersion containerCommonVersion; startAddresses = startAddresses ?? Enumerable.Empty <Address>(); (containers, containerCommonVersion) = ProcessContainers(startContainers); List <Address> fetchAddrs = startAddresses .Where(sa => !containers.Any(kvp => kvp.Key.Address == sa)).ToList(); List <IGrouping <Type, Address> > allStartAddressesByType = null; if (DoCollation) { allStartAddressesByType = fetchAddrs.Concat(containers.Keys) .GroupBy(a => a.Type) .ToList(); // Get all addresses for items to collate (startAddresses plus addresses from startContainers) foreach (var addrTypeG in allStartAddressesByType) { Type contentType = addrTypeG.Key; var rpsAttributes = contentType .GetCustomAttributes(typeof(RedirectPropertySourceAttribute), true) .Cast <RedirectPropertySourceAttribute>() .ToList(); foreach (Address addr in addrTypeG) { fetchAddrs.AddRange(rpsAttributes .Select(attr => new Address(attr.ContentType ?? contentType, PathFunctions.Redirect(addr.GetAsContentPath(), attr.SourceDescriptor)))); } } fetchAddrs = fetchAddrs.Distinct().ToList(); } bool pushVersion = (startContainers != null && DoCollation); if (pushVersion) // Get containers in any version that might be relevant to a start container { System.Versions.PushState(VersioningMode.Specific, containerCommonVersion); } try { // Get all the containers for collation (if current version is not fully specified, may be multiple per address) foreach (var cont in System.Repository.Get(typeof(object), fetchAddrs)) { var va = new VersionedAddress(System, cont); if (containers.ContainsKey(va)) { log.Error("Duplicate versioned address in db: " + va.ToString()); } else { containers.Add(new VersionedAddress(new Address(cont), new ItemVersion(System, cont).Canonicalise()), cont); } } } finally { if (pushVersion) { System.Versions.PopState(); } } if (!DoCollation) { // just return the containers we have foreach (object cont in containers.Values) { yield return(cont as T); } yield break; } // Create a lookup by (non-versioned) address of all the containers we have var contLookup = containers.ToLookup(kvp => kvp.Key.Address.ToString(), kvp => kvp.Value); // We have the data, now collate it into the content from the startContainers foreach (var addrTypeG in allStartAddressesByType) { // Process all the start addresses (including those of the start containers) of a given type Type contentType = addrTypeG.Key; var rpsAttributes = contentType .GetCustomAttributes(typeof(RedirectPropertySourceAttribute), true) .Cast <RedirectPropertySourceAttribute>() .ToList(); foreach (var addr in addrTypeG.Select(a => new Address(a.Type, a)).Distinct()) // convert a VersionedAddress to an Address if necessary { var primaryPath = addr.GetAsContentPath(); if (!contLookup.Contains(new Address(addr.Type, addr).ToString())) { continue; } foreach (var cont in contLookup[addr.ToString()]) { object primaryContent = cont; if (primaryContent is IContentContainer) { primaryContent = ((IContentContainer)primaryContent).GetContent(System.Extender); } foreach (var rpsAttribute in rpsAttributes) { var refAddress = new VersionedAddress( rpsAttribute.ContentType ?? contentType, PathFunctions.Redirect(primaryPath, rpsAttribute.SourceDescriptor), new ItemVersion(System, cont).Canonicalise() ); if (refAddress.Address == addr) // redirected to itself, ignore { continue; } object refItem = containers.ContainsKey(refAddress) ? containers[refAddress] : null; if (refItem is IContentContainer) { refItem = ((IContentContainer)refItem).GetContent(System.Extender); } if (refItem != null) { foreach (string propertyPath in rpsAttribute.PropertyPaths) { var toFromPaths = GetPaths(propertyPath); object val = ReflectionX.GetPropertyValueByPath(refItem, toFromPaths[1]); var piSet = ReflectionX.GetPropertyByPath(primaryContent.GetType(), toFromPaths[0]); piSet.SetValue(primaryContent, val); } } } yield return(primaryContent as T); } } } }
/// <summary> /// Decollates changes to content object which should be redirected to other records used as property sources /// </summary> /// <param name="path">path of content record</param> /// <param name="data">content object</param> /// <returns>JObject build from content object</returns> protected virtual object SetRelated(string path, object data, bool bypassChecks) { if (!DoCollation) { return(null); } System.Versions.PushState(VersioningMode.Specific, new ItemVersion(System, data)); try { JObject jObjectContent = JObject.FromObject(data); // Establish the records to fetch and fetch them Type contentType = data.GetType().UnextendedType(); var rpsAttributes = contentType .GetCustomAttributes(typeof(RedirectPropertySourceAttribute), true) .Cast <RedirectPropertySourceAttribute>() .Where(rpsa => !rpsa.ReadOnly) .ToList(); List <Address> addresses = rpsAttributes .Select(a => new Address(a.ContentType ?? contentType, PathFunctions.Redirect(path, a.SourceDescriptor))) .Distinct() .ToList(); if (addresses == null || addresses.Count == 0) { return(data); } List <object> records = System.Repository.Get(typeof(object), addresses).ToList(); // Update the fetched referenced records with updated referenced properties on the content object List <Address> doneAddrs = new List <Address>(); List <object> vals = new List <object>(); var writebacks = new Dictionary <string[], object>(); foreach (var rpsAttribute in rpsAttributes) { Address address = new Address( rpsAttribute.ContentType ?? contentType, PathFunctions.Redirect(path, rpsAttribute.SourceDescriptor)); string refdPath = address.GetAsContentPath(); Type refdType = address.Type; if (refdPath == path && refdType == contentType) // redirected to itself, ignore { continue; } object refdRecord = records.FirstOrDefault(r => new Address(r) == address); object refdContent = refdRecord; if (refdRecord is IContentContainer) { refdContent = ((IContentContainer)refdRecord).GetContent(System.Extender); } if (refdRecord == null) // adding a new record { refdContent = System.Collator.GetNew(address); refdRecord = System.Collator.GetContainer(address, refdContent); } JObject refdObject = JObject.FromObject(refdContent); List <string[]> writebackPaths = new List <string[]>(); foreach (string propertyPath in rpsAttribute.PropertyPaths) { var toFromPaths = GetPaths(propertyPath); if (toFromPaths[0].EndsWith("<")) { toFromPaths[0] = toFromPaths[0].UpToLast("<"); toFromPaths[1] = toFromPaths[1].UpToLast("<"); writebackPaths.Add(toFromPaths); } refdObject.CopyPropertyFrom(toFromPaths[1], jObjectContent, toFromPaths[0]); } if (refdRecord is IContentContainer) { Type valType = ((IContentContainer)refdRecord).ContentType; valType = System.Extender[valType] ?? valType; ((IContentContainer)refdRecord).SetContent(System, refdObject.ToObject(valType)); } else { refdRecord = refdObject.ToObject(refdRecord.GetType()); } if (!doneAddrs.Contains(address)) { doneAddrs.Add(address); vals.Add(refdRecord); } writebackPaths.Do(wp => writebacks.Add(wp, refdRecord)); } // Create or update referred-to records if (vals.Count > 0) { Repository.Set(vals, new Dictionary <string, object> { { "bypassChecks", bypassChecks }, { "viaCollation", true } }); // write back any values configured by attributes (e.g. database index updates) foreach (var kvp in writebacks) { JObject refdObject = JObject.FromObject(kvp.Value); jObjectContent.CopyPropertyFrom(kvp.Key[0], refdObject, kvp.Key[1]); } data = jObjectContent.ToObject(data.GetType()); } return(writebacks.Count > 0 ? data : null); } finally { System.Versions.PopState(); } }
/// <summary> /// Starting from a list of addresses and optionally (or only) the containers at those addresses, fetch /// any containers necessary and any other containers required to supply redirected properties for them, /// obtain the contained content items and collate their properties, returning the content items at the /// addresses. /// </summary> /// <typeparam name="T">Type of content items to return</typeparam> /// <param name="startContainers">Initial list of containers if they are available</param> /// <param name="startAddresses">Initial list of addresses, which may be omitted and derived from containers</param> /// <returns>List of content items</returns> public IEnumerable <T> Collate <T>(IEnumerable <object> startContainers, IEnumerable <Address> startAddresses) where T : class { // place to store all the containers we have currently var containers = new Dictionary <VersionedAddress, object>(); ItemVersion containerCommonVersion = null; // Ensure we have the start addresses if (startContainers != null) { containers = startContainers.ToDictionary(sc => new VersionedAddress(sc), sc => sc); startAddresses = containers.Keys.Select(va => va.Address).Distinct().ToList(); containerCommonVersion = ItemVersion.LeastAbstractCommonVersion(containers.Keys.Select(va => va.Version)); } var fetchAddrs = startAddresses .GroupBy(a => a.Type.GetCustomAttributes <RedirectPropertySourceAttribute>()) .SelectMany(ag => ag.SelectMany(a => ag.Key .Select(attr => attr.Redirect(a)) .Concat(a))) .Distinct() .Except(containers.Keys.Select(va => va.Address)) .ToList(); bool pushVersion = (startContainers != null); if (pushVersion) // Get containers in any version that might be relevant to a start container { VersionManager.Instance.PushState(VersioningMode.Specific, containerCommonVersion); } try { // Get all the containers for collation (if current version is not fully specified, may be multiple per address) foreach (var cont in Repository.Instance.Get(typeof(object), fetchAddrs)) { var va = new VersionedAddress(cont); if (containers.ContainsKey(va)) { log.Error("Duplicate versioned address in db: " + va.ToString()); } else { containers.Add(new VersionedAddress(cont), cont); } } } finally { if (pushVersion) { VersionManager.Instance.PopState(); } } var contLookup = containers.ToLookup(kvp => kvp.Key.Address.ToString(), kvp => kvp.Value); if (startContainers == null) { startContainers = startAddresses.SelectMany(a => contLookup[a.ToString()]); } // We have the data, now collate it into the content from the startContainers foreach (var addrTypeG in startAddresses.GroupBy(a => a.Type)) { // Process all the start addresses of a given type Type contentType = addrTypeG.Key; var rpsAttributes = contentType .GetCustomAttributes(typeof(RedirectPropertySourceAttribute), false) .Cast <RedirectPropertySourceAttribute>() .ToList(); foreach (var addr in addrTypeG) { var primaryPath = addr.GetAsContentPath(); if (!contLookup.Contains(addr.ToString())) { continue; } foreach (var cont in contLookup[addr.ToString()]) { object primaryContent = cont; if (primaryContent is IContentContainer) { primaryContent = ((IContentContainer)primaryContent).GetContent(); } foreach (var rpsAttribute in rpsAttributes) { var refAddress = new VersionedAddress( rpsAttribute.ContentType ?? contentType, PathFunctions.Redirect(primaryPath, rpsAttribute.SourceDescriptor), new ItemVersion(cont) ); if (refAddress.Address == addr) // redirected to itself, ignore { continue; } object refItem = containers.ContainsKey(refAddress) ? containers[refAddress] : null; if (refItem is IContentContainer) { refItem = ((IContentContainer)refItem).GetContent(); } if (refItem != null) { foreach (string propertyPath in rpsAttribute.PropertyPaths) { var toFromPaths = GetPaths(propertyPath); object val = ReflectionX.GetPropertyValueByPath(refItem, toFromPaths[1]); var piSet = ReflectionX.GetPropertyByPath(primaryContent.GetType(), toFromPaths[0]); piSet.SetValue(primaryContent, val); } } } yield return(primaryContent as T); } } } }