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
{
///
/// 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, CacheItemPriority priority = CacheItemPriority.Normal, 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, priority, dependentFiles);
// clone / reset to go into the cache
return CheckCloneableAndTracksChanges(cached);
}
///
public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, 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, priority, 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;
}
}
}