using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Cache { /// /// Implements by wrapping an inner other /// instance, and ensuring that all inserts and returns are deep cloned copies of the cache item, /// when the item is deep-cloneable. /// internal class DeepCloneAppCache : IAppPolicyCache { /// /// Initializes a new instance of the class. /// public DeepCloneAppCache(IAppPolicyCache innerCache) { var type = typeof (DeepCloneAppCache); if (innerCache.GetType() == type) throw new InvalidOperationException($"A {type} cannot wrap another instance of itself."); InnerCache = innerCache; } /// /// Gets the inner cache. /// public IAppPolicyCache InnerCache { get; } /// public object Get(string key) { var item = InnerCache.Get(key); return CheckCloneableAndTracksChanges(item); } /// public object Get(string key, Func factory) { var cached = InnerCache.Get(key, () => { var result = FastDictionaryAppCacheBase.GetSafeLazy(factory); var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache // do not store null values (backward compat), clone / reset to go into the cache return value == null ? null : CheckCloneableAndTracksChanges(value); }); return CheckCloneableAndTracksChanges(cached); } /// public IEnumerable SearchByKey(string keyStartsWith) { return InnerCache.SearchByKey(keyStartsWith) .Select(CheckCloneableAndTracksChanges); } /// public IEnumerable SearchByRegex(string regex) { return InnerCache.SearchByRegex(regex) .Select(CheckCloneableAndTracksChanges); } /// public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, string[] dependentFiles = null) { var cached = InnerCache.Get(key, () => { var result = FastDictionaryAppCacheBase.GetSafeLazy(factory); var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache // do not store null values (backward compat), clone / reset to go into the cache return value == null ? null : CheckCloneableAndTracksChanges(value); // clone / reset to go into the cache }, timeout, isSliding, dependentFiles); // clone / reset to go into the cache return CheckCloneableAndTracksChanges(cached); } /// public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, string[] dependentFiles = null) { InnerCache.Insert(key, () => { var result = FastDictionaryAppCacheBase.GetSafeLazy(factory); var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache // do not store null values (backward compat), clone / reset to go into the cache return value == null ? null : CheckCloneableAndTracksChanges(value); }, timeout, isSliding, dependentFiles); } /// public void Clear() { InnerCache.Clear(); } /// public void Clear(string key) { InnerCache.Clear(key); } /// public void ClearOfType(string typeName) { InnerCache.ClearOfType(typeName); } /// public void ClearOfType() { InnerCache.ClearOfType(); } /// public void ClearOfType(Func predicate) { InnerCache.ClearOfType(predicate); } /// public void ClearByKey(string keyStartsWith) { InnerCache.ClearByKey(keyStartsWith); } /// public void ClearByRegex(string regex) { InnerCache.ClearByRegex(regex); } private static object CheckCloneableAndTracksChanges(object input) { if (input is IDeepCloneable cloneable) { input = cloneable.DeepClone(); } // reset dirty initial properties if (input is IRememberBeingDirty tracksChanges) { tracksChanges.ResetDirtyProperties(false); input = tracksChanges; } return input; } } }