using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Extensions; namespace Umbraco.Cms.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. /// public class DeepCloneAppCache : IAppPolicyCache, IDisposable { private bool _disposedValue; /// /// Initializes a new instance of the class. /// public DeepCloneAppCache(IAppPolicyCache innerCache) { Type type = typeof(DeepCloneAppCache); if (innerCache.GetType() == type) { throw new InvalidOperationException($"A {type} cannot wrap another instance of itself."); } InnerCache = innerCache; } /// /// Gets the inner cache. /// private 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, () => { Lazy result = SafeLazy.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) => InnerCache.SearchByKey(keyStartsWith) .Select(CheckCloneableAndTracksChanges); /// public IEnumerable SearchByRegex(string regex) => 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, () => { Lazy result = SafeLazy.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, () => { Lazy result = SafeLazy.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(Type type) => InnerCache.ClearOfType(type); /// 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); public void Dispose() => // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(true); protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { InnerCache.DisposeIfDisposable(); } _disposedValue = true; } } 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; } }