using System; using System.Collections.Generic; using System.Linq; using System.Web.Caching; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Cache { /// /// Interface describing this cache provider as a wrapper for another /// internal interface IRuntimeCacheProviderWrapper { IRuntimeCacheProvider InnerProvider { get; } } /// /// A wrapper for any IRuntimeCacheProvider that ensures that all inserts and returns /// are a deep cloned copy of the item when the item is IDeepCloneable and that tracks changes are /// reset if the object is TracksChangesEntityBase /// internal class DeepCloneRuntimeCacheProvider : IRuntimeCacheProvider, IRuntimeCacheProviderWrapper { public IRuntimeCacheProvider InnerProvider { get; } public DeepCloneRuntimeCacheProvider(IRuntimeCacheProvider innerProvider) { var type = typeof (DeepCloneRuntimeCacheProvider); if (innerProvider.GetType() == type) throw new InvalidOperationException($"A {type} cannot wrap another instance of {type}."); InnerProvider = innerProvider; } #region Clear - doesn't require any changes public void ClearAllCache() { InnerProvider.ClearAllCache(); } public void ClearCacheItem(string key) { InnerProvider.ClearCacheItem(key); } public void ClearCacheObjectTypes(string typeName) { InnerProvider.ClearCacheObjectTypes(typeName); } public void ClearCacheObjectTypes() { InnerProvider.ClearCacheObjectTypes(); } public void ClearCacheObjectTypes(Func predicate) { InnerProvider.ClearCacheObjectTypes(predicate); } public void ClearCacheByKeySearch(string keyStartsWith) { InnerProvider.ClearCacheByKeySearch(keyStartsWith); } public void ClearCacheByKeyExpression(string regexString) { InnerProvider.ClearCacheByKeyExpression(regexString); } #endregion public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { return InnerProvider.GetCacheItemsByKeySearch(keyStartsWith) .Select(CheckCloneableAndTracksChanges); } public IEnumerable GetCacheItemsByKeyExpression(string regexString) { return InnerProvider.GetCacheItemsByKeyExpression(regexString) .Select(CheckCloneableAndTracksChanges); } public object GetCacheItem(string cacheKey) { var item = InnerProvider.GetCacheItem(cacheKey); return CheckCloneableAndTracksChanges(item); } public object GetCacheItem(string cacheKey, Func getCacheItem) { var cached = InnerProvider.GetCacheItem(cacheKey, () => { var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache if (value == null) return null; // do not store null values (backward compat) return CheckCloneableAndTracksChanges(value); }); return CheckCloneableAndTracksChanges(cached); } public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { var cached = InnerProvider.GetCacheItem(cacheKey, () => { var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache if (value == null) return null; // do not store null values (backward compat) // clone / reset to go into the cache return CheckCloneableAndTracksChanges(value); }, timeout, isSliding, priority, removedCallback, dependentFiles); // clone / reset to go into the cache return CheckCloneableAndTracksChanges(cached); } public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { InnerProvider.InsertCacheItem(cacheKey, () => { var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem); var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache if (value == null) return null; // do not store null values (backward compat) // clone / reset to go into the cache return CheckCloneableAndTracksChanges(value); }, timeout, isSliding, priority, removedCallback, dependentFiles); } private static object CheckCloneableAndTracksChanges(object input) { var cloneable = input as IDeepCloneable; if (cloneable != null) { input = cloneable.DeepClone(); } // reset dirty initial properties (U4-1946) var tracksChanges = input as IRememberBeingDirty; if (tracksChanges != null) { tracksChanges.ResetDirtyProperties(false); input = tracksChanges; } return input; } } }