Files
Umbraco-CMS/src/Umbraco.Web/PublishedCache/NuCache/Property.cs

302 lines
13 KiB
C#
Raw Normal View History

2016-05-27 14:26:28 +02:00
using System;
2017-12-07 13:22:32 +01:00
using System.Collections.Generic;
2016-05-27 14:26:28 +02:00
using System.Xml.Serialization;
2018-04-28 21:57:07 +02:00
using Umbraco.Core;
2016-05-27 14:26:28 +02:00
using Umbraco.Core.Cache;
using Umbraco.Core.Collections;
2018-04-28 21:57:07 +02:00
using Umbraco.Core.Models;
2016-05-27 14:26:28 +02:00
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
2017-12-07 13:22:32 +01:00
using Umbraco.Web.PublishedCache.NuCache.DataSource;
2016-05-27 14:26:28 +02:00
namespace Umbraco.Web.PublishedCache.NuCache
{
[Serializable]
[XmlType(Namespace = "http://umbraco.org/webservices/")]
2017-07-12 14:09:31 +02:00
internal class Property : PublishedPropertyBase
2016-05-27 14:26:28 +02:00
{
2017-10-31 12:48:24 +01:00
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
2016-05-27 14:26:28 +02:00
private readonly Guid _contentUid;
private readonly bool _isPreviewing;
private readonly bool _isMember;
private readonly PublishedContent _content;
2018-04-28 21:57:07 +02:00
private readonly ContentVariation _variations;
2016-05-27 14:26:28 +02:00
private readonly object _locko = new object();
2016-05-27 14:26:28 +02:00
// the invariant-neutral source and inter values
2017-12-07 13:22:32 +01:00
private readonly object _sourceValue;
private bool _interInitialized;
private object _interValue;
2017-12-07 13:22:32 +01:00
// the variant source and inter values
2018-04-21 09:57:28 +02:00
private Dictionary<CompositeStringStringKey, SourceInterValue> _sourceValues;
2017-12-07 13:22:32 +01:00
// the variant and non-variant object values
private CacheValues _cacheValues;
2017-12-07 13:22:32 +01:00
private string _valuesCacheKey;
2016-05-27 14:26:28 +02:00
// initializes a published content property with no value
2019-04-15 17:14:45 +02:00
public Property(IPublishedPropertyType propertyType, PublishedContent content, IPublishedSnapshotAccessor publishedSnapshotAccessor, PropertyCacheLevel referenceCacheLevel = PropertyCacheLevel.Element)
2017-10-31 12:48:24 +01:00
: this(propertyType, content, null, publishedSnapshotAccessor, referenceCacheLevel)
2016-05-27 14:26:28 +02:00
{ }
// initializes a published content property with a value
2019-04-15 17:14:45 +02:00
public Property(IPublishedPropertyType propertyType, PublishedContent content, PropertyData[] sourceValues, IPublishedSnapshotAccessor publishedSnapshotAccessor, PropertyCacheLevel referenceCacheLevel = PropertyCacheLevel.Element)
: base(propertyType, referenceCacheLevel)
2016-05-27 14:26:28 +02:00
{
2017-12-07 13:22:32 +01:00
if (sourceValues != null)
{
foreach (var sourceValue in sourceValues)
{
if (sourceValue.Culture == "" && sourceValue.Segment == "")
2017-12-07 13:22:32 +01:00
{
_sourceValue = sourceValue.Value;
}
else
{
if (_sourceValues == null)
2018-04-21 09:57:28 +02:00
_sourceValues = new Dictionary<CompositeStringStringKey, SourceInterValue>();
_sourceValues[new CompositeStringStringKey(sourceValue.Culture, sourceValue.Segment)]
= new SourceInterValue { Culture = sourceValue.Culture, Segment = sourceValue.Segment, SourceValue = sourceValue.Value };
2017-12-07 13:22:32 +01:00
}
}
}
2016-05-27 14:26:28 +02:00
_contentUid = content.Key;
_content = content;
_isPreviewing = content.IsPreviewing;
2016-05-27 14:26:28 +02:00
_isMember = content.ContentType.ItemType == PublishedItemType.Member;
2017-10-31 12:48:24 +01:00
_publishedSnapshotAccessor = publishedSnapshotAccessor;
2018-04-28 21:57:07 +02:00
_variations = propertyType.Variations;
2016-05-27 14:26:28 +02:00
}
// clone for previewing as draft a published content that is published and has no draft
public Property(Property origin, PublishedContent content)
: base(origin.PropertyType, origin.ReferenceCacheLevel)
2016-05-27 14:26:28 +02:00
{
_sourceValue = origin._sourceValue;
_sourceValues = origin._sourceValues;
2017-12-07 13:22:32 +01:00
2016-05-27 14:26:28 +02:00
_contentUid = origin._contentUid;
_content = content;
2016-05-27 14:26:28 +02:00
_isPreviewing = true;
_isMember = origin._isMember;
2017-10-31 12:48:24 +01:00
_publishedSnapshotAccessor = origin._publishedSnapshotAccessor;
2018-04-28 21:57:07 +02:00
_variations = origin._variations;
2016-05-27 14:26:28 +02:00
}
// determines whether a property has value
2018-05-08 11:06:07 +02:00
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;
lock (_locko)
{
value = GetInterValue(culture, segment);
hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter);
if (hasValue.HasValue) return hasValue.Value;
var 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;
}
2018-05-08 11:06:07 +02:00
}
2016-05-27 14:26:28 +02:00
// used to cache the CacheValues of this property
internal string ValuesCacheKey => _valuesCacheKey
?? (_valuesCacheKey = CacheKeys.PropertyCacheValues(_contentUid, Alias, _isPreviewing));
2016-05-27 14:26:28 +02:00
private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)
2016-05-27 14:26:28 +02:00
{
CacheValues cacheValues;
2018-04-27 11:38:50 +10:00
PublishedSnapshot publishedSnapshot;
2019-01-17 11:01:23 +01:00
IAppCache cache;
2016-05-27 14:26:28 +02:00
switch (cacheLevel)
{
case PropertyCacheLevel.None:
// never cache anything
cacheValues = new CacheValues();
2016-05-27 14:26:28 +02:00
break;
2017-10-31 12:48:24 +01:00
case PropertyCacheLevel.Element:
2016-05-27 14:26:28 +02:00
// cache within the property object itself, ie within the content object
cacheValues = _cacheValues ?? (_cacheValues = new CacheValues());
2016-05-27 14:26:28 +02:00
break;
2017-10-31 12:48:24 +01:00
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
2016-05-27 14:26:28 +02:00
// data) depending on settings
2017-10-31 12:48:24 +01:00
// for members, always cache in the snapshot cache - never pollute elements cache
2018-04-27 11:38:50 +10:00
publishedSnapshot = (PublishedSnapshot) _publishedSnapshotAccessor.PublishedSnapshot;
2017-10-31 12:48:24 +01:00
cache = publishedSnapshot == null
? null
2017-10-31 12:48:24 +01:00
: ((_isPreviewing == false || PublishedSnapshotService.FullCacheWhenPreviewing) && (_isMember == false)
? publishedSnapshot.ElementsCache
: publishedSnapshot.SnapshotCache);
cacheValues = GetCacheValues(cache);
2016-05-27 14:26:28 +02:00
break;
2017-10-31 12:48:24 +01:00
case PropertyCacheLevel.Snapshot:
// cache within the snapshot cache
2018-04-27 11:38:50 +10:00
publishedSnapshot = (PublishedSnapshot) _publishedSnapshotAccessor.PublishedSnapshot;
2017-10-31 12:48:24 +01:00
cache = publishedSnapshot?.SnapshotCache;
cacheValues = GetCacheValues(cache);
2016-05-27 14:26:28 +02:00
break;
default:
throw new InvalidOperationException("Invalid cache level.");
}
return cacheValues;
2016-05-27 14:26:28 +02:00
}
2019-01-17 11:01:23 +01:00
private CacheValues GetCacheValues(IAppCache cache)
2016-05-27 14:26:28 +02:00
{
if (cache == null) // no cache, don't cache
return new CacheValues();
2019-01-17 11:01:23 +01:00
return (CacheValues) cache.Get(ValuesCacheKey, () => new CacheValues());
2016-05-27 14:26:28 +02:00
}
2017-12-07 13:22:32 +01:00
// this is always invoked from within a lock, so does not require its own lock
2018-04-21 09:57:28 +02:00
private object GetInterValue(string culture, string segment)
2016-05-27 14:26:28 +02:00
{
if (culture == "" && segment == "")
2017-12-07 13:22:32 +01:00
{
if (_interInitialized) return _interValue;
_interValue = PropertyType.ConvertSourceToInter(_content, _sourceValue, _isPreviewing);
_interInitialized = true;
return _interValue;
}
if (_sourceValues == null)
2018-04-21 09:57:28 +02:00
_sourceValues = new Dictionary<CompositeStringStringKey, SourceInterValue>();
2017-12-07 13:22:32 +01:00
2018-04-21 09:57:28 +02:00
var k = new CompositeStringStringKey(culture, segment);
if (!_sourceValues.TryGetValue(k, out var vvalue))
2018-05-02 09:31:30 +02:00
_sourceValues[k] = vvalue = new SourceInterValue { Culture = culture, Segment = segment, SourceValue = GetSourceValue(culture, segment) };
2017-12-07 13:22:32 +01:00
if (vvalue.InterInitialized) return vvalue.InterValue;
vvalue.InterValue = PropertyType.ConvertSourceToInter(_content, vvalue.SourceValue, _isPreviewing);
vvalue.InterInitialized = true;
return vvalue.InterValue;
2016-05-27 14:26:28 +02:00
}
public override object GetSourceValue(string culture = null, string segment = null)
2017-12-07 13:22:32 +01:00
{
_content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment);
if (culture == "" && segment == "")
2017-12-07 13:22:32 +01:00
return _sourceValue;
lock (_locko)
{
if (_sourceValues == null) return null;
2018-04-21 09:57:28 +02:00
return _sourceValues.TryGetValue(new CompositeStringStringKey(culture, segment), out var sourceValue) ? sourceValue.SourceValue : null;
2017-12-07 13:22:32 +01:00
}
}
2016-05-27 14:26:28 +02:00
public override object GetValue(string culture = null, string segment = null)
2016-05-27 14:26:28 +02:00
{
_content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment);
object value;
2017-12-06 11:51:35 +01:00
lock (_locko)
2016-05-27 14:26:28 +02:00
{
2018-04-21 09:57:28 +02:00
var cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment);
2017-12-06 11:51:35 +01:00
// initial reference cache level always is .Content
const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;
if (cacheValues.ObjectInitialized) return cacheValues.ObjectValue;
2018-04-21 09:57:28 +02:00
cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing);
2017-12-06 11:51:35 +01:00
cacheValues.ObjectInitialized = true;
value = cacheValues.ObjectValue;
2016-05-27 14:26:28 +02:00
}
return value;
2016-05-27 14:26:28 +02:00
}
public override object GetXPathValue(string culture = null, string segment = null)
2016-05-27 14:26:28 +02:00
{
_content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment);
2017-12-06 11:51:35 +01:00
lock (_locko)
2016-05-27 14:26:28 +02:00
{
2018-04-21 09:57:28 +02:00
var cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment);
2017-12-06 11:51:35 +01:00
// initial reference cache level always is .Content
const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;
if (cacheValues.XPathInitialized) return cacheValues.XPathValue;
2018-04-21 09:57:28 +02:00
cacheValues.XPathValue = PropertyType.ConvertInterToXPath(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing);
2017-12-06 11:51:35 +01:00
cacheValues.XPathInitialized = true;
return cacheValues.XPathValue;
2016-05-27 14:26:28 +02:00
}
}
2017-12-07 13:22:32 +01:00
#region Classes
private class CacheValue
2017-12-07 13:22:32 +01:00
{
public bool ObjectInitialized { get; set; }
public object ObjectValue { get; set; }
public bool XPathInitialized { get; set; }
public object XPathValue { get; set; }
}
2017-12-07 13:22:32 +01:00
private class CacheValues : CacheValue
{
2018-04-21 09:57:28 +02:00
private Dictionary<CompositeStringStringKey, CacheValue> _values;
2017-12-07 13:22:32 +01:00
// this is always invoked from within a lock, so does not require its own lock
2018-04-21 09:57:28 +02:00
public CacheValue For(string culture, string segment)
2017-12-07 13:22:32 +01:00
{
if (culture == "" && segment == "")
2017-12-07 13:22:32 +01:00
return this;
if (_values == null)
2018-04-21 09:57:28 +02:00
_values = new Dictionary<CompositeStringStringKey, CacheValue>();
2017-12-07 13:22:32 +01:00
2018-04-21 09:57:28 +02:00
var k = new CompositeStringStringKey(culture, segment);
if (!_values.TryGetValue(k, out var value))
_values[k] = value = new CacheValue();
2017-12-07 13:22:32 +01:00
return value;
2017-12-07 13:22:32 +01:00
}
}
private class SourceInterValue
2017-12-07 13:22:32 +01:00
{
2018-04-21 09:57:28 +02:00
private string _culture;
private string _segment;
2018-04-21 09:57:28 +02:00
public string Culture
{
get => _culture;
internal set => _culture = value?.ToLowerInvariant();
}
public string Segment
{
get => _segment;
internal set => _segment = value?.ToLowerInvariant();
}
2017-12-07 13:22:32 +01:00
public object SourceValue { get; set; }
public bool InterInitialized { get; set; }
public object InterValue { get; set; }
}
#endregion
2016-05-27 14:26:28 +02:00
}
}