diff --git a/src/Umbraco.Cms.Api.Delivery/Json/DeliveryApiJsonTypeResolver.cs b/src/Umbraco.Cms.Api.Delivery/Json/DeliveryApiJsonTypeResolver.cs index 10f052485a..b22e7c9341 100644 --- a/src/Umbraco.Cms.Api.Delivery/Json/DeliveryApiJsonTypeResolver.cs +++ b/src/Umbraco.Cms.Api.Delivery/Json/DeliveryApiJsonTypeResolver.cs @@ -12,23 +12,36 @@ public class DeliveryApiJsonTypeResolver : DefaultJsonTypeInfoResolver { JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options); - if (jsonTypeInfo.Type == typeof(IApiContent)) + Type[] derivedTypes = GetDerivedTypes(jsonTypeInfo); + if (derivedTypes.Length > 0) { - ConfigureJsonPolymorphismOptions(jsonTypeInfo, typeof(ApiContent)); - } - else if (jsonTypeInfo.Type == typeof(IApiContentResponse)) - { - ConfigureJsonPolymorphismOptions(jsonTypeInfo, typeof(ApiContentResponse)); - } - else if (jsonTypeInfo.Type == typeof(IRichTextElement)) - { - ConfigureJsonPolymorphismOptions(jsonTypeInfo, typeof(RichTextRootElement), typeof(RichTextGenericElement), typeof(RichTextTextElement)); + ConfigureJsonPolymorphismOptions(jsonTypeInfo, derivedTypes); } return jsonTypeInfo; } - private void ConfigureJsonPolymorphismOptions(JsonTypeInfo jsonTypeInfo, params Type[] derivedTypes) + protected virtual Type[] GetDerivedTypes(JsonTypeInfo jsonTypeInfo) + { + if (jsonTypeInfo.Type == typeof(IApiContent)) + { + return new[] { typeof(ApiContent) }; + } + + if (jsonTypeInfo.Type == typeof(IApiContentResponse)) + { + return new[] { typeof(ApiContentResponse) }; + } + + if (jsonTypeInfo.Type == typeof(IRichTextElement)) + { + return new[] { typeof(RichTextRootElement), typeof(RichTextGenericElement), typeof(RichTextTextElement) }; + } + + return Array.Empty(); + } + + protected void ConfigureJsonPolymorphismOptions(JsonTypeInfo jsonTypeInfo, params Type[] derivedTypes) { jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions { diff --git a/src/Umbraco.Core/DeliveryApi/ApiContentResponseBuilder.cs b/src/Umbraco.Core/DeliveryApi/ApiContentResponseBuilder.cs index a551115a1e..68bb01c012 100644 --- a/src/Umbraco.Core/DeliveryApi/ApiContentResponseBuilder.cs +++ b/src/Umbraco.Core/DeliveryApi/ApiContentResponseBuilder.cs @@ -1,11 +1,10 @@ using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Routing; using Umbraco.Extensions; namespace Umbraco.Cms.Core.DeliveryApi; -public sealed class ApiContentResponseBuilder : ApiContentBuilderBase, IApiContentResponseBuilder +public class ApiContentResponseBuilder : ApiContentBuilderBase, IApiContentResponseBuilder { private readonly IApiContentRouteBuilder _apiContentRouteBuilder; @@ -14,6 +13,12 @@ public sealed class ApiContentResponseBuilder : ApiContentBuilderBase _apiContentRouteBuilder = apiContentRouteBuilder; protected override IApiContentResponse Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary properties) + { + IDictionary cultures = GetCultures(content); + return new ApiContentResponse(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties, cultures); + } + + protected virtual IDictionary GetCultures(IPublishedContent content) { var routesByCulture = new Dictionary(); @@ -35,6 +40,6 @@ public sealed class ApiContentResponseBuilder : ApiContentBuilderBase? _sourceValues; + private readonly object _locko = new(); + private ConcurrentDictionary? _sourceValues; private string? _valuesCacheKey; @@ -66,12 +67,9 @@ internal class Property : PublishedPropertyBase } else { - if (_sourceValues == null) - { - _sourceValues = new Dictionary(); - } + EnsureSourceValuesInitialized(); - _sourceValues[new CompositeStringStringKey(sourceValue.Culture, sourceValue.Segment)] + _sourceValues![new CompositeStringStringKey(sourceValue.Culture, sourceValue.Segment)] = new SourceInterValue { Culture = sourceValue.Culture, @@ -125,30 +123,27 @@ internal class Property : PublishedPropertyBase return hasValue.Value; } - lock (_locko) + value = GetInterValue(culture, segment); + hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter); + if (hasValue.HasValue) { - value = GetInterValue(culture, segment); - hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter); - if (hasValue.HasValue) - { - return hasValue.Value; - } - - CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); - - // initial reference cache level always is .Content - const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; - - if (!cacheValues.ObjectInitialized) - { - cacheValues.ObjectValue = - PropertyType.ConvertInterToObject(_content, initialCacheLevel, value, _isPreviewing); - cacheValues.ObjectInitialized = true; - } - - value = cacheValues.ObjectValue; - return PropertyType.IsValue(value, PropertyValueLevel.Object) ?? false; + return hasValue.Value; } + + CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); + + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + + if (!cacheValues.ObjectInitialized) + { + cacheValues.ObjectValue = + PropertyType.ConvertInterToObject(_content, initialCacheLevel, value, _isPreviewing); + cacheValues.ObjectInitialized = true; + } + + value = cacheValues.ObjectValue; + return PropertyType.IsValue(value, PropertyValueLevel.Object) ?? false; } public override object? GetSourceValue(string? culture = null, string? segment = null) @@ -160,19 +155,16 @@ internal class Property : PublishedPropertyBase return _sourceValue; } - lock (_locko) + if (_sourceValues == null) { - if (_sourceValues == null) - { - return null; - } - - return _sourceValues.TryGetValue( - new CompositeStringStringKey(culture, segment), - out SourceInterValue? sourceValue) - ? sourceValue.SourceValue - : null; + return null; } + + return _sourceValues.TryGetValue( + new CompositeStringStringKey(culture, segment), + out SourceInterValue? sourceValue) + ? sourceValue.SourceValue + : null; } private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel) @@ -227,7 +219,6 @@ internal class Property : PublishedPropertyBase return (CacheValues)cache.Get(ValuesCacheKey, () => new CacheValues())!; } - // this is always invoked from within a lock, so does not require its own lock private object? GetInterValue(string? culture, string? segment) { if (culture == string.Empty && segment == string.Empty) @@ -242,21 +233,17 @@ internal class Property : PublishedPropertyBase return _interValue; } - if (_sourceValues == null) - { - _sourceValues = new Dictionary(); - } + EnsureSourceValuesInitialized(); var k = new CompositeStringStringKey(culture, segment); - if (!_sourceValues.TryGetValue(k, out SourceInterValue? vvalue)) - { - _sourceValues[k] = vvalue = new SourceInterValue + + SourceInterValue vvalue = _sourceValues!.GetOrAdd(k, _ => + new SourceInterValue { Culture = culture, Segment = segment, SourceValue = GetSourceValue(culture, segment), - }; - } + }); if (vvalue.InterInitialized) { @@ -273,23 +260,20 @@ internal class Property : PublishedPropertyBase _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); object? value; - lock (_locko) + CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); + + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + + if (cacheValues.ObjectInitialized) { - 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 cacheValues.ObjectValue; } + cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); + cacheValues.ObjectInitialized = true; + value = cacheValues.ObjectValue; + return value; } @@ -298,22 +282,19 @@ internal class Property : PublishedPropertyBase { _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); - lock (_locko) + CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); + + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + + if (cacheValues.XPathInitialized) { - CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); - - // initial reference cache level always is .Content - const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; - - if (cacheValues.XPathInitialized) - { - return cacheValues.XPathValue; - } - - cacheValues.XPathValue = PropertyType.ConvertInterToXPath(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); - cacheValues.XPathInitialized = true; return cacheValues.XPathValue; } + + cacheValues.XPathValue = PropertyType.ConvertInterToXPath(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); + cacheValues.XPathInitialized = true; + return cacheValues.XPathValue; } public override object? GetDeliveryApiValue(bool expanding, string? culture = null, string? segment = null) @@ -321,18 +302,16 @@ internal class Property : PublishedPropertyBase _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); object? value; - lock (_locko) - { - CacheValue cacheValues = GetCacheValues(expanding ? PropertyType.DeliveryApiCacheLevelForExpansion : PropertyType.DeliveryApiCacheLevel).For(culture, segment); + 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); - } + // 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; } @@ -382,9 +361,9 @@ internal class Property : PublishedPropertyBase private class CacheValues : CacheValue { - private Dictionary? _values; + private readonly object _locko = new(); + private ConcurrentDictionary? _values; - // this is always invoked from within a lock, so does not require its own lock public CacheValue For(string? culture, string? segment) { if (culture == string.Empty && segment == string.Empty) @@ -394,14 +373,15 @@ internal class Property : PublishedPropertyBase if (_values == null) { - _values = new Dictionary(); + lock (_locko) + { + _values ??= InitializeConcurrentDictionary(); + } } var k = new CompositeStringStringKey(culture, segment); - if (!_values.TryGetValue(k, out CacheValue? value)) - { - _values[k] = value = new CacheValue(); - } + + CacheValue value = _values.GetOrAdd(k, _ => new CacheValue()); return value; } @@ -431,5 +411,22 @@ internal class Property : PublishedPropertyBase public object? InterValue { get; set; } } + private static ConcurrentDictionary InitializeConcurrentDictionary() + where TKey : notnull + => new(-1, 5); + + private void EnsureSourceValuesInitialized() + { + if (_sourceValues is not null) + { + return; + } + + lock (_locko) + { + _sourceValues ??= InitializeConcurrentDictionary(); + } + } + #endregion } diff --git a/version.json b/version.json index f70819e8b4..6ab0f37af9 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "13.3.0-rc", + "version": "13.3.0", "assemblyVersion": { "precision": "build" },