Files
Umbraco-CMS/src/Umbraco.PublishedCache.HybridCache/PublishedProperty.cs
Mole 1258962429 V15: Remove Nucache (#17166)
* Remove nucache reference from Web.Common

* Get tests building-ish

* Move ReservedFieldNamesService to the right project

* Remove IPublishedSnapshotStatus

* Added functionality to the INavigationQueryService to get root keys

* Fixed issue with navigation

* Remove IPublishedSnapshot from UmbracoContext

* Begin removing usage of IPublishedSnapshot from PublishedContentExtensions

* Fix PublishedContentExtensions.cs

* Don't use snapshots in delivery media api

* Use IPublishedMediaCache in QueryMediaApiController

* Remove more usages of IPublishedSnapshotAccessor

* Comment out tests

* Remove more usages of PublishedSnapshotAccessor

* Remove PublishedSnapshot from property

* Fixed test build

* Fix errors

* Fix some tests

* Delete NuCache 🎉

* Implement DatabaseCacheRebuilder

* Remove usage of IPublishedSnapshotService

* Remove IPublishedSnapshotService

* Remove TestPublishedSnapshotAccessor and make tests build

* Don't test Snapshot cachelevel

It's no longer supported

* Fix BlockEditorConverter

Element != Element document type

* Remember to set cachemanager

* Fix RichTextParserTests

* Implement TryGetLevel on INavigationQueryService

* Fake level and obsolete it in PublishedContent

* Remove ChildrenForAllCultures

* Hack Path property on PublishedContent

* Remove usages of IPublishedSnapshot in tests

* More ConvertersTests

* Add hybrid cache to integration tests

We can actually do this now because we no longer save files on disk

* Rename IPublishedSnapshotRebuilder to ICacheRebuilder

* Comment out tests

* V15: Replacing the usages of Parent (navigation data) from IPublishedContent (#17125)

* Fix .Parent references in PublishedContentExtensions

* Add missing methods to FriendlyPublishedContentExtensions (ones that you were able to call on the content directly as they now require extra params)

* Fix references from the extension methods

* Fix dependencies in tests

* Replace IPublishedSnapshotAccessor with the content cache in tests

* Resolving more .Parent references

* Fix unit tests

* Obsolete and use extension methods

* Remove private method and use extension instead

* Moving code around

* Fix tests

* Fix more references

* Cleanup

* Fix more usages

* Resolve merge conflict

* Fix tests

* Cleanup

* Fix more tests

* Fixed unit tests

* Cleanup

* Replace last usages

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>

* Remove usage of IPublishedSnapshotAccessor from IRequestItemProvider

* Post merge fixup

* Remo IPublishedSnapshot

* Add HasAny to IDocumentUrlService

* Fix TextBuilder

* Fix modelsbuilder tests

* Use explicit types

* Implement GetByContentType

* Support element types in PublishedContentTypeCache

* Run enlistments before publishing notifications

* Fix elements cache refreshing

* Implement GetByUdi

* Implement GetAtRoot

* Implement GetByRoute

* Reimplement GetRouteById

* Fix blocks unit tests

* Initialize domain cache on boot

* Only return routes with domains on non default lanauges

* V15: Replacing the usages of `Children` (navigation data) from `IPublishedContent` (#17159)

* Update params in PublishedContentExtensions to the general interfaces for the published cache and navigation service, so that we can use the extension methods on both documents and media

* Introduce GetParent() which uses the right services

* Fix obsolete message on .Parent

* Obsolete .Children

* Fix usages of Children for ApiMediaQueryService

* Fix usage in internal

* Fix usages in views

* Fix indentation

* Fix issue with delete language

* Update nuget pacakges

* Clear elements cache when content is deleted

instead of trying to update it

* Reset publishedModelFactory

* Fixed publishing

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>
Co-authored-by: kjac <kja@umbraco.dk>
2024-10-01 15:03:02 +02:00

331 lines
12 KiB
C#

using System.Collections.Concurrent;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Collections;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.HybridCache;
internal class PublishedProperty : PublishedPropertyBase
{
private readonly PublishedContent _content;
private readonly bool _isPreviewing;
private readonly IElementsCache _elementsCache;
private readonly bool _isMember;
private string? _valuesCacheKey;
// the invariant-neutral source and inter values
private readonly object? _sourceValue;
private readonly ContentVariation _variations;
private readonly ContentVariation _sourceVariations;
// the variant and non-variant object values
private bool _interInitialized;
private object? _interValue;
private CacheValues? _cacheValues;
// the variant source and inter values
private readonly object _locko = new();
private ConcurrentDictionary<CompositeStringStringKey, SourceInterValue>? _sourceValues;
// initializes a published content property with a value
public PublishedProperty(
IPublishedPropertyType propertyType,
PublishedContent content,
PropertyData[]? sourceValues,
IElementsCache elementsElementsCache,
PropertyCacheLevel referenceCacheLevel = PropertyCacheLevel.Element)
: base(propertyType, referenceCacheLevel)
{
if (sourceValues != null)
{
foreach (PropertyData sourceValue in sourceValues)
{
if (sourceValue.Culture == string.Empty && sourceValue.Segment == string.Empty)
{
_sourceValue = sourceValue.Value;
}
else
{
EnsureSourceValuesInitialized();
_sourceValues![new CompositeStringStringKey(sourceValue.Culture, sourceValue.Segment)]
= new SourceInterValue
{
Culture = sourceValue.Culture,
Segment = sourceValue.Segment,
SourceValue = sourceValue.Value,
};
}
}
}
_content = content;
_isPreviewing = content.IsPreviewing;
_isMember = content.ContentType.ItemType == PublishedItemType.Member;
_elementsCache = elementsElementsCache;
// this variable is used for contextualizing the variation level when calculating property values.
// it must be set to the union of variance (the combination of content type and property type variance).
_variations = propertyType.Variations | content.ContentType.Variations;
_sourceVariations = propertyType.Variations;
}
// used to cache the CacheValues of this property
internal string ValuesCacheKey => _valuesCacheKey ??= PropertyCacheValues(_content.Key, Alias, _isPreviewing);
private string PropertyCacheValues(Guid contentUid, string typeAlias, bool previewing)
{
if (previewing)
{
return CacheKeys.PreviewPropertyCacheKeyPrefix + contentUid + ":" + typeAlias + "]";
}
return CacheKeys.PropertyCacheKeyPrefix + contentUid + ":" + typeAlias + "]";
}
// determines whether a property has value
public override bool HasValue(string? culture = null, string? segment = null)
{
_content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment);
var value = GetSourceValue(culture, segment);
var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source);
if (hasValue.HasValue)
{
return hasValue.Value;
}
return PropertyType.IsValue(GetInterValue(culture, segment), PropertyValueLevel.Object) ?? false;
}
public override object? GetSourceValue(string? culture = null, string? segment = null)
{
_content.VariationContextAccessor.ContextualizeVariation(_sourceVariations, _content.Id, ref culture, ref segment);
// source values are tightly bound to the property/schema culture and segment configurations, so we need to
// sanitize the contextualized culture/segment states before using them to access the source values.
culture = _sourceVariations.VariesByCulture() ? culture : string.Empty;
segment = _sourceVariations.VariesBySegment() ? segment : string.Empty;
if (culture == string.Empty && segment == string.Empty)
{
return _sourceValue;
}
if (_sourceValues == null)
{
return null;
}
return _sourceValues.TryGetValue(
new CompositeStringStringKey(culture, segment),
out SourceInterValue? sourceValue)
? sourceValue.SourceValue
: null;
}
private object? GetInterValue(string? culture, string? segment)
{
if (culture is "" && segment is "")
{
if (_interInitialized)
{
return _interValue;
}
_interValue = PropertyType.ConvertSourceToInter(_content, _sourceValue, _isPreviewing);
_interInitialized = true;
return _interValue;
}
return PropertyType.ConvertSourceToInter(_content, GetSourceValue(culture, segment), _isPreviewing);
}
public override object? GetValue(string? culture = null, string? segment = null)
{
_content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment);
object? value;
CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment);
// initial reference cache level always is .Content
const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;
if (cacheValues.ObjectInitialized)
{
return cacheValues.ObjectValue;
}
cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing);
cacheValues.ObjectInitialized = true;
value = cacheValues.ObjectValue;
return value;
}
private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)
{
CacheValues cacheValues;
IAppCache? cache;
switch (cacheLevel)
{
case PropertyCacheLevel.None:
// never cache anything
cacheValues = new CacheValues();
break;
case PropertyCacheLevel.Snapshot: // Snapshot is obsolete, so for now treat as element
case PropertyCacheLevel.Element:
// cache within the property object itself, ie within the content object
cacheValues = _cacheValues ??= new CacheValues();
break;
case PropertyCacheLevel.Elements:
// cache within the elements cache, unless previewing, then use the snapshot or
// elements cache (if we don't want to pollute the elements cache with short-lived
// data) depending on settings
// for members, always cache in the snapshot cache - never pollute elements cache
cache = _isMember == false ? _elementsCache : null;
cacheValues = GetCacheValues(cache);
break;
default:
throw new InvalidOperationException("Invalid cache level.");
}
return cacheValues;
}
private CacheValues GetCacheValues(IAppCache? cache)
{
// no cache, don't cache
if (cache == null)
{
return new CacheValues();
}
return (CacheValues)cache.Get(ValuesCacheKey, () => new CacheValues())!;
}
public override object? GetDeliveryApiValue(bool expanding, string? culture = null, string? segment = null)
{
_content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment);
object? value;
CacheValue cacheValues = GetCacheValues(expanding ? PropertyType.DeliveryApiCacheLevelForExpansion : PropertyType.DeliveryApiCacheLevel).For(culture, segment);
// initial reference cache level always is .Content
const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;
object? GetDeliveryApiObject() => PropertyType.ConvertInterToDeliveryApiObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing, expanding);
value = expanding
? GetDeliveryApiExpandedObject(cacheValues, GetDeliveryApiObject)
: GetDeliveryApiDefaultObject(cacheValues, GetDeliveryApiObject);
return value;
}
private object? GetDeliveryApiDefaultObject(CacheValue cacheValues, Func<object?> getValue)
{
if (cacheValues.DeliveryApiDefaultObjectInitialized == false)
{
cacheValues.DeliveryApiDefaultObjectValue = getValue();
cacheValues.DeliveryApiDefaultObjectInitialized = true;
}
return cacheValues.DeliveryApiDefaultObjectValue;
}
private object? GetDeliveryApiExpandedObject(CacheValue cacheValues, Func<object?> getValue)
{
if (cacheValues.DeliveryApiExpandedObjectInitialized == false)
{
cacheValues.DeliveryApiExpandedObjectValue = getValue();
cacheValues.DeliveryApiExpandedObjectInitialized = true;
}
return cacheValues.DeliveryApiExpandedObjectValue;
}
private class SourceInterValue
{
private string? _culture;
private string? _segment;
public string? Culture
{
get => _culture;
internal set => _culture = value?.ToLowerInvariant();
}
public string? Segment
{
get => _segment;
internal set => _segment = value?.ToLowerInvariant();
}
public object? SourceValue { get; set; }
}
private class CacheValues : CacheValue
{
private readonly object _locko = new();
private ConcurrentDictionary<CompositeStringStringKey, CacheValue>? _values;
public CacheValue For(string? culture, string? segment)
{
if (culture == string.Empty && segment == string.Empty)
{
return this;
}
if (_values == null)
{
lock (_locko)
{
_values ??= InitializeConcurrentDictionary<CompositeStringStringKey, CacheValue>();
}
}
var k = new CompositeStringStringKey(culture, segment);
CacheValue value = _values.GetOrAdd(k, _ => new CacheValue());
return value;
}
}
private class CacheValue
{
public bool ObjectInitialized { get; set; }
public object? ObjectValue { get; set; }
public bool DeliveryApiDefaultObjectInitialized { get; set; }
public object? DeliveryApiDefaultObjectValue { get; set; }
public bool DeliveryApiExpandedObjectInitialized { get; set; }
public object? DeliveryApiExpandedObjectValue { get; set; }
}
private static ConcurrentDictionary<TKey, TValue> InitializeConcurrentDictionary<TKey, TValue>()
where TKey : notnull
=> new(-1, 5);
private void EnsureSourceValuesInitialized()
{
if (_sourceValues is not null)
{
return;
}
lock (_locko)
{
_sourceValues ??= InitializeConcurrentDictionary<CompositeStringStringKey, SourceInterValue>();
}
}
}