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;
}
}