From 1560e84f1418b59ea9e1e784dfe8edf3a945eb34 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 25 Oct 2022 18:40:11 +0200 Subject: [PATCH] Add IContextCache to deploy connectors (#13287) * Add IContextCache and implementations * Update connector interfaces to use IContextCache * Minor cleanup * Move DeployContextCache prefix to constant * Move default implementations to obsolete methods * Remove DeployContextCache and DictionaryCache Co-authored-by: Andy Butland --- src/Umbraco.Core/Deploy/ArtifactBase.cs | 89 +++++++++--------- .../Deploy/ArtifactDeployState.cs | 37 ++++++++ .../ArtifactDeployStateOfTArtifactTEntity.cs | 38 -------- src/Umbraco.Core/Deploy/IContextCache.cs | 31 +++++++ .../Deploy/IDataTypeConfigurationConnector.cs | 62 +++++++++---- src/Umbraco.Core/Deploy/IServiceConnector.cs | 93 +++++++++++++------ src/Umbraco.Core/Deploy/IValueConnector.cs | 57 +++++++++--- src/Umbraco.Core/Deploy/PassThroughCache.cs | 34 +++++++ .../Deploy/IGridCellValueConnector.cs | 71 ++++++++++---- .../Umbraco.Core/CoreThings/UdiTests.cs | 4 +- 10 files changed, 359 insertions(+), 157 deletions(-) delete mode 100644 src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs create mode 100644 src/Umbraco.Core/Deploy/IContextCache.cs create mode 100644 src/Umbraco.Core/Deploy/PassThroughCache.cs diff --git a/src/Umbraco.Core/Deploy/ArtifactBase.cs b/src/Umbraco.Core/Deploy/ArtifactBase.cs index cc2415f4cd..0d354b65de 100644 --- a/src/Umbraco.Core/Deploy/ArtifactBase.cs +++ b/src/Umbraco.Core/Deploy/ArtifactBase.cs @@ -1,50 +1,49 @@ -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Provides a base class to all artifacts. +/// +public abstract class ArtifactBase : IArtifact + where TUdi : Udi { - /// - /// Provides a base class to all artifacts. - /// - public abstract class ArtifactBase : IArtifact - where TUdi : Udi + protected ArtifactBase(TUdi udi, IEnumerable? dependencies = null) { - protected ArtifactBase(TUdi udi, IEnumerable? dependencies = null) - { - Udi = udi ?? throw new ArgumentNullException("udi"); - Name = Udi.ToString(); + Udi = udi ?? throw new ArgumentNullException("udi"); + Name = Udi.ToString(); - _dependencies = dependencies ?? Enumerable.Empty(); - _checksum = new Lazy(GetChecksum); - } - - private readonly Lazy _checksum; - - private IEnumerable _dependencies; - - protected abstract string GetChecksum(); - - Udi IArtifactSignature.Udi => Udi; - - public TUdi Udi { get; set; } - - public string Checksum => _checksum.Value; - - /// - /// Prevents the property from being serialized. - /// - /// - /// Note that we can't use here as that works only on fields, not properties. And we want to avoid using [JsonIgnore] - /// as that would require an external dependency in Umbraco.Cms.Core. - /// So using this method of excluding properties from serialized data, documented here: https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm - /// - public bool ShouldSerializeChecksum() => false; - - public IEnumerable Dependencies - { - get => _dependencies; - set => _dependencies = value.OrderBy(x => x.Udi); - } - - public string Name { get; set; } - - public string Alias { get; set; } = string.Empty; + _dependencies = dependencies ?? Enumerable.Empty(); + _checksum = new Lazy(GetChecksum); } + + private readonly Lazy _checksum; + + private IEnumerable _dependencies; + + protected abstract string GetChecksum(); + + Udi IArtifactSignature.Udi => Udi; + + public TUdi Udi { get; set; } + + public string Checksum => _checksum.Value; + + /// + /// Prevents the property from being serialized. + /// + /// + /// Note that we can't use here as that works only on fields, not properties. And we want to avoid using [JsonIgnore] + /// as that would require an external dependency in Umbraco.Cms.Core. + /// So using this method of excluding properties from serialized data, documented here: https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm + /// + public bool ShouldSerializeChecksum() => false; + + public IEnumerable Dependencies + { + get => _dependencies; + set => _dependencies = value.OrderBy(x => x.Udi); + } + + public string Name { get; set; } + + public string Alias { get; set; } = string.Empty; } diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs index 1b75fe11c0..e2dd343af1 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs @@ -44,3 +44,40 @@ public abstract class ArtifactDeployState /// protected abstract IArtifact GetArtifactAsIArtifact(); } + +/// +/// Represent the state of an artifact being deployed. +/// +/// The type of the artifact. +/// The type of the entity. +public class ArtifactDeployState : ArtifactDeployState + where TArtifact : IArtifact +{ + /// + /// Initializes a new instance of the class. + /// + /// The artifact. + /// The entity. + /// The service connector deploying the artifact. + /// The next pass number. + public ArtifactDeployState(TArtifact art, TEntity? entity, IServiceConnector connector, int nextPass) + { + Artifact = art; + Entity = entity; + Connector = connector; + NextPass = nextPass; + } + + /// + /// Gets or sets the artifact. + /// + public new TArtifact Artifact { get; set; } + + /// + /// Gets or sets the entity. + /// + public TEntity? Entity { get; set; } + + /// + protected sealed override IArtifact GetArtifactAsIArtifact() => Artifact; +} diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs b/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs deleted file mode 100644 index 0ff1e20e87..0000000000 --- a/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Umbraco.Cms.Core.Deploy; - -/// -/// Represent the state of an artifact being deployed. -/// -/// The type of the artifact. -/// The type of the entity. -public class ArtifactDeployState : ArtifactDeployState - where TArtifact : IArtifact -{ - /// - /// Initializes a new instance of the class. - /// - /// The artifact. - /// The entity. - /// The service connector deploying the artifact. - /// The next pass number. - public ArtifactDeployState(TArtifact art, TEntity? entity, IServiceConnector connector, int nextPass) - { - Artifact = art; - Entity = entity; - Connector = connector; - NextPass = nextPass; - } - - /// - /// Gets or sets the artifact. - /// - public new TArtifact Artifact { get; set; } - - /// - /// Gets or sets the entity. - /// - public TEntity? Entity { get; set; } - - /// - protected sealed override IArtifact GetArtifactAsIArtifact() => Artifact; -} diff --git a/src/Umbraco.Core/Deploy/IContextCache.cs b/src/Umbraco.Core/Deploy/IContextCache.cs new file mode 100644 index 0000000000..d175064905 --- /dev/null +++ b/src/Umbraco.Core/Deploy/IContextCache.cs @@ -0,0 +1,31 @@ +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Represents a context cache used by Deploy operations. +/// +public interface IContextCache +{ + /// + /// Creates the item on the context cache using the specified . + /// + /// The type of the cached item. + /// The key of the cached item. + /// The item. + void Create(string key, T item); + + /// + /// Gets an item from the context cache or creates and stores it using the specified . + /// + /// The type of the cached item. + /// The key of the cached item. + /// The factory method to create the item (if it doesn't exist yet). + /// + /// The item. + /// + T? GetOrCreate(string key, Func factory); + + /// + /// Clears all cached items on this context. + /// + void Clear(); +} diff --git a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs index 6b91926b57..506d1f5745 100644 --- a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs +++ b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs @@ -1,37 +1,67 @@ -using System.Diagnostics.CodeAnalysis; using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Deploy; /// -/// Defines methods that can convert data type configuration to / from an environment-agnostic string. +/// Defines methods that can convert data type configuration to / from an environment-agnostic string. /// /// -/// Configuration may contain values such as content identifiers, that would be local -/// to one environment, and need to be converted in order to be deployed. +/// Configuration may contain values such as content identifiers, that would be local +/// to one environment, and need to be converted in order to be deployed. /// -[SuppressMessage( - "ReSharper", - "UnusedMember.Global", - Justification = "This is actual only used by Deploy, but we don't want third parties to have references on deploy, that's why this interface is part of core.")] public interface IDataTypeConfigurationConnector { /// - /// Gets the property editor aliases that the value converter supports by default. + /// Gets the property editor aliases that the value converter supports by default. /// + /// + /// The property editor aliases. + /// IEnumerable PropertyEditorAliases { get; } /// - /// Gets the artifact datatype configuration corresponding to the actual datatype configuration. + /// Gets the artifact configuration value corresponding to a data type configuration and gather dependencies. /// - /// The datatype. + /// The data type. /// The dependencies. - string? ToArtifact(IDataType dataType, ICollection dependencies); + /// + /// The artifact configuration value. + /// + [Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")] + string? ToArtifact(IDataType dataType, ICollection dependencies) + => ToArtifact(dataType, dependencies, PassThroughCache.Instance); /// - /// Gets the actual datatype configuration corresponding to the artifact configuration. + /// Gets the artifact configuration value corresponding to a data type configuration and gather dependencies. /// - /// The datatype. - /// The artifact configuration. - object? FromArtifact(IDataType dataType, string? configuration); + /// The data type. + /// The dependencies. + /// The context cache. + /// + /// The artifact configuration value. + /// + string? ToArtifact(IDataType dataType, ICollection dependencies, IContextCache contextCache); + + /// + /// Gets the data type configuration corresponding to an artifact configuration value. + /// + /// The data type. + /// The artifact configuration value. + /// + /// The data type configuration. + /// + [Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")] + object? FromArtifact(IDataType dataType, string? configuration) + => FromArtifact(dataType, configuration, PassThroughCache.Instance); + + /// + /// Gets the data type configuration corresponding to an artifact configuration value. + /// + /// The data type. + /// The artifact configuration value. + /// The context cache. + /// + /// The data type configuration. + /// + object? FromArtifact(IDataType dataType, string? configuration, IContextCache contextCache); } diff --git a/src/Umbraco.Core/Deploy/IServiceConnector.cs b/src/Umbraco.Core/Deploy/IServiceConnector.cs index f6cd7c8002..b4f530fb35 100644 --- a/src/Umbraco.Core/Deploy/IServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IServiceConnector.cs @@ -3,34 +3,65 @@ using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Deploy; /// -/// Connects to an Umbraco service. +/// Connects to an Umbraco service. /// +/// public interface IServiceConnector : IDiscoverable { /// - /// Gets an artifact. + /// Gets an artifact. /// /// The entity identifier of the artifact. - /// The corresponding artifact, or null. - IArtifact? GetArtifact(Udi udi); + /// + /// The corresponding artifact, or null. + /// + [Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")] + IArtifact? GetArtifact(Udi udi) + => GetArtifact(udi, PassThroughCache.Instance); /// - /// Gets an artifact. + /// Gets an artifact. + /// + /// The entity identifier of the artifact. + /// The context cache. + /// + /// The corresponding artifact, or null. + /// + IArtifact? GetArtifact(Udi udi, IContextCache contextCache); + + /// + /// Gets an artifact. /// /// The entity. - /// The corresponding artifact. - IArtifact GetArtifact(object entity); + /// + /// The corresponding artifact. + /// + [Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")] + IArtifact GetArtifact(object entity) + => GetArtifact(entity, PassThroughCache.Instance); /// - /// Initializes processing for an artifact. + /// Gets an artifact. + /// + /// The entity. + /// The context cache. + /// + /// The corresponding artifact. + /// + IArtifact GetArtifact(object entity, IContextCache contextCache); + + /// + /// Initializes processing for an artifact. /// /// The artifact. /// The deploy context. - /// The mapped artifact. + /// + /// The mapped artifact. + /// ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context); /// - /// Processes an artifact. + /// Processes an artifact. /// /// The mapped artifact. /// The deploy context. @@ -38,46 +69,56 @@ public interface IServiceConnector : IDiscoverable void Process(ArtifactDeployState dart, IDeployContext context, int pass); /// - /// Explodes a range into udis. + /// Explodes a range into udis. /// /// The range. /// The list of udis where to add the new udis. - /// Also, it's cool to have a method named Explode. Kaboom! + /// + /// Also, it's cool to have a method named Explode. Kaboom! + /// void Explode(UdiRange range, List udis); /// - /// Gets a named range for a specified udi and selector. + /// Gets a named range for a specified udi and selector. /// /// The udi. /// The selector. - /// The named range for the specified udi and selector. + /// + /// The named range for the specified udi and selector. + /// NamedUdiRange GetRange(Udi udi, string selector); /// - /// Gets a named range for specified entity type, identifier and selector. + /// Gets a named range for specified entity type, identifier and selector. /// /// The entity type. /// The identifier. /// The selector. - /// The named range for the specified entity type, identifier and selector. + /// + /// The named range for the specified entity type, identifier and selector. + /// /// - /// This is temporary. At least we thought it would be, in sept. 2016. What day is it now? - /// - /// At the moment our UI has a hard time returning proper udis, mainly because Core's tree do - /// not manage guids but only ints... so we have to provide a way to support it. The string id here - /// can be either a real string (for string udis) or an "integer as a string", using the value "-1" to - /// indicate the "root" i.e. an open udi. - /// + /// This is temporary. At least we thought it would be, in sept. 2016. What day is it now? + /// + /// At the moment our UI has a hard time returning proper udis, mainly because Core's tree do + /// not manage guids but only ints... so we have to provide a way to support it. The string id here + /// can be either a real string (for string udis) or an "integer as a string", using the value "-1" to + /// indicate the "root" i.e. an open udi. + /// /// NamedUdiRange GetRange(string entityType, string sid, string selector); /// - /// Compares two artifacts. + /// Compares two artifacts. /// /// The first artifact. /// The second artifact. /// A collection of differences to append to, if not null. - /// A boolean value indicating whether the artifacts are identical. - /// ServiceConnectorBase{TArtifact} provides a very basic default implementation. + /// + /// A boolean value indicating whether the artifacts are identical. + /// + /// + /// ServiceConnectorBase{TArtifact} provides a very basic default implementation. + /// bool Compare(IArtifact? art1, IArtifact? art2, ICollection? differences = null); } diff --git a/src/Umbraco.Core/Deploy/IValueConnector.cs b/src/Umbraco.Core/Deploy/IValueConnector.cs index f2a776c7ca..9f3b17f71c 100644 --- a/src/Umbraco.Core/Deploy/IValueConnector.cs +++ b/src/Umbraco.Core/Deploy/IValueConnector.cs @@ -3,35 +3,70 @@ using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Deploy; /// -/// Defines methods that can convert a property value to / from an environment-agnostic string. +/// Defines methods that can convert a property value to and from an environment-agnostic string. /// /// -/// Property values may contain values such as content identifiers, that would be local -/// to one environment, and need to be converted in order to be deployed. Connectors also deal -/// with serializing to / from string. +/// Property values may contain values such as content identifiers, that would be local +/// to one environment and need to be converted in order to be deployed. Connectors also deal +/// with serializing and deserializing the content value to an environment-agnostic string. /// public interface IValueConnector { /// - /// Gets the property editor aliases that the value converter supports by default. + /// Gets the property editor aliases that the value converter supports by default. /// + /// + /// The property editor aliases. + /// IEnumerable PropertyEditorAliases { get; } /// - /// Gets the deploy property value corresponding to a content property value, and gather dependencies. + /// Gets the deploy property value corresponding to a content property value, and gather dependencies. /// /// The content property value. /// The value property type /// The content dependencies. - /// The deploy property value. - string? ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies); + /// + /// The deploy property value. + /// + [Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")] + string? ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies) + => ToArtifact(value, propertyType, dependencies, PassThroughCache.Instance); /// - /// Gets the content property value corresponding to a deploy property value. + /// Gets the deploy property value corresponding to a content property value, and gather dependencies. + /// + /// The content property value. + /// The value property type + /// The content dependencies. + /// The context cache. + /// + /// The deploy property value. + /// + string? ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies, IContextCache contextCache); + + /// + /// Gets the content property value corresponding to a deploy property value. /// /// The deploy property value. /// The value property type /// The current content property value. - /// The content property value. - object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue); + /// + /// The content property value. + /// + [Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")] + object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue) + => FromArtifact(value, propertyType, currentValue, PassThroughCache.Instance); + + /// + /// Gets the content property value corresponding to a deploy property value. + /// + /// The deploy property value. + /// The value property type + /// The current content property value. + /// The context cache. + /// + /// The content property value. + /// + object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue, IContextCache contextCache); } diff --git a/src/Umbraco.Core/Deploy/PassThroughCache.cs b/src/Umbraco.Core/Deploy/PassThroughCache.cs new file mode 100644 index 0000000000..064fc10ad5 --- /dev/null +++ b/src/Umbraco.Core/Deploy/PassThroughCache.cs @@ -0,0 +1,34 @@ +namespace Umbraco.Cms.Core.Deploy; + +/// +/// A pass through context cache that always creates the items. +/// +/// +public sealed class PassThroughCache : IContextCache +{ + /// + /// Gets the instance. + /// + /// + /// The instance. + /// + public static PassThroughCache Instance { get; } = new PassThroughCache(); + + /// + /// Prevents a default instance of the class from being created. + /// + private PassThroughCache() + { } + + /// + public void Create(string key, T item) + { } + + /// + public T? GetOrCreate(string key, Func factory) + => factory(); + + /// + public void Clear() + { } +} diff --git a/src/Umbraco.Infrastructure/Deploy/IGridCellValueConnector.cs b/src/Umbraco.Infrastructure/Deploy/IGridCellValueConnector.cs index eb1bc518ac..a9ed5fd84d 100644 --- a/src/Umbraco.Infrastructure/Deploy/IGridCellValueConnector.cs +++ b/src/Umbraco.Infrastructure/Deploy/IGridCellValueConnector.cs @@ -1,44 +1,77 @@ using Umbraco.Cms.Core.Models; +using static Umbraco.Cms.Core.Models.GridValue; namespace Umbraco.Cms.Core.Deploy; /// -/// Defines methods that can convert a grid cell value to / from an environment-agnostic string. +/// Defines methods that can convert a grid cell value to / from an environment-agnostic string. /// /// -/// Grid cell values may contain values such as content identifiers, that would be local -/// to one environment, and need to be converted in order to be deployed. +/// Grid cell values may contain values such as content identifiers, that would be local +/// to one environment, and need to be converted in order to be deployed. /// public interface IGridCellValueConnector { /// - /// Gets a value indicating whether the connector supports a specified grid editor view. + /// Gets a value indicating whether the connector supports a specified grid editor view. /// - /// - /// The grid editor view. It needs to be the view instead of the alias as the view is really what - /// identifies what kind of connector should be used. Alias can be anything and you can have multiple different aliases - /// using the same kind of view. - /// - /// A value indicating whether the connector supports the grid editor view. - /// Note that can be string.Empty to indicate the "default" connector. + /// The grid editor view. It needs to be the view instead of the alias as the view is really what + /// identifies what kind of connector should be used. Alias can be anything and you can have multiple different aliases + /// using the same kind of view. + /// + /// true if the specified view is connector; otherwise, false. + /// + /// + /// A value indicating whether the connector supports the grid editor view. + /// bool IsConnector(string view); /// - /// Gets the value to be deployed from the control value as a string. + /// Gets the value to be deployed from the control value as a string. /// /// The control containing the value. /// The dependencies of the property. - /// The grid cell value to be deployed. - /// Note that - string? GetValue(GridValue.GridControl gridControl, ICollection dependencies); + /// + /// The grid cell value to be deployed. + /// + /// + /// Note that + /// + [Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")] + string? GetValue(GridValue.GridControl gridControl, ICollection dependencies) + => GetValue(gridControl, dependencies, PassThroughCache.Instance); /// - /// Allows you to modify the value of a control being deployed. + /// Gets the value to be deployed from the control value as a string. + /// + /// The control containing the value. + /// The dependencies of the property. + /// The context cache. + /// + /// The grid cell value to be deployed. + /// + string? GetValue(GridValue.GridControl gridControl, ICollection dependencies, IContextCache contextCache); + + /// + /// Allows you to modify the value of a control being deployed. /// /// The control being deployed. /// - /// Follows the pattern of the property value connectors (). The SetValue method is - /// used to modify the value of the . + /// Follows the pattern of the property value connectors (). + /// The SetValue method is used to modify the value of the . /// - void SetValue(GridValue.GridControl gridControl); + [Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")] + void SetValue(GridValue.GridControl gridControl) + => SetValue(gridControl, PassThroughCache.Instance); + + /// + /// Allows you to modify the value of a control being deployed. + /// + /// The control being deployed. + /// The context cache. + /// + /// Follows the pattern of the property value connectors (). + /// The SetValue method is used to modify the value of the . + /// + void SetValue(GridValue.GridControl gridControl, IContextCache contextCache); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs index 1596bf9888..3cc54f4933 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs @@ -307,9 +307,9 @@ public class UdiTests [UdiDefinition("foo", UdiType.GuidUdi)] public class FooConnector : IServiceConnector { - public IArtifact GetArtifact(Udi udi) => throw new NotImplementedException(); + public IArtifact GetArtifact(Udi udi, IContextCache contextCache) => throw new NotImplementedException(); - public IArtifact GetArtifact(object entity) => throw new NotImplementedException(); + public IArtifact GetArtifact(object entity, IContextCache contextCache) => throw new NotImplementedException(); public ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context) => throw new NotImplementedException();