2021-03-12 21:48:24 +01:00
using Umbraco.Cms.Core.Events ;
2021-02-18 11:06:02 +01:00
using Umbraco.Cms.Core.Models ;
2021-05-11 14:33:49 +02:00
using Umbraco.Cms.Core.Notifications ;
2021-02-18 11:06:02 +01:00
using Umbraco.Cms.Core.Persistence.Repositories ;
using Umbraco.Cms.Core.PublishedCache ;
using Umbraco.Cms.Core.Serialization ;
using Umbraco.Cms.Core.Services ;
using Umbraco.Cms.Core.Services.Changes ;
2024-09-27 09:12:19 +02:00
using Umbraco.Cms.Core.Services.Navigation ;
2021-02-18 11:06:02 +01:00
using Umbraco.Extensions ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
namespace Umbraco.Cms.Core.Cache ;
public sealed class ContentCacheRefresher : PayloadCacheRefresherBase < ContentCacheRefresherNotification ,
ContentCacheRefresher . JsonPayload >
2016-05-26 17:12:04 +02:00
{
2022-06-07 15:28:38 +02:00
private readonly IDomainService _domainService ;
2024-10-01 15:03:02 +02:00
private readonly IDomainCacheService _domainCacheService ;
2024-09-27 09:12:19 +02:00
private readonly IDocumentUrlService _documentUrlService ;
private readonly IDocumentNavigationQueryService _documentNavigationQueryService ;
2024-09-30 16:43:05 +02:00
private readonly IDocumentNavigationManagementService _documentNavigationManagementService ;
private readonly IContentService _contentService ;
2024-10-28 15:31:39 +01:00
private readonly IDocumentCacheService _documentCacheService ;
2024-10-15 19:33:23 +02:00
private readonly IPublishStatusManagementService _publishStatusManagementService ;
2022-06-07 15:28:38 +02:00
private readonly IIdKeyMap _idKeyMap ;
public ContentCacheRefresher (
AppCaches appCaches ,
IJsonSerializer serializer ,
2024-09-27 09:12:19 +02:00
IIdKeyMap idKeyMap ,
IDomainService domainService ,
IEventAggregator eventAggregator ,
ICacheRefresherNotificationFactory factory ,
IDocumentUrlService documentUrlService ,
2024-10-01 15:03:02 +02:00
IDomainCacheService domainCacheService ,
2024-09-30 16:43:05 +02:00
IDocumentNavigationQueryService documentNavigationQueryService ,
IDocumentNavigationManagementService documentNavigationManagementService ,
2024-10-15 19:33:23 +02:00
IContentService contentService ,
2024-10-28 15:31:39 +01:00
IPublishStatusManagementService publishStatusManagementService ,
IDocumentCacheService documentCacheService )
2022-06-07 15:28:38 +02:00
: base ( appCaches , serializer , eventAggregator , factory )
2016-05-26 17:12:04 +02:00
{
2022-06-07 15:28:38 +02:00
_idKeyMap = idKeyMap ;
_domainService = domainService ;
2024-10-01 15:03:02 +02:00
_domainCacheService = domainCacheService ;
2024-09-27 09:12:19 +02:00
_documentUrlService = documentUrlService ;
_documentNavigationQueryService = documentNavigationQueryService ;
2024-09-30 16:43:05 +02:00
_documentNavigationManagementService = documentNavigationManagementService ;
_contentService = contentService ;
2024-10-28 15:31:39 +01:00
_documentCacheService = documentCacheService ;
2024-10-15 19:33:23 +02:00
_publishStatusManagementService = publishStatusManagementService ;
2022-06-07 15:28:38 +02:00
}
#region Indirect
public static void RefreshContentTypes ( AppCaches appCaches )
{
// we could try to have a mechanism to notify the PublishedCachesService
// and figure out whether published items were modified or not... keep it
// simple for now, just clear the whole thing
appCaches . ClearPartialViewCache ( ) ;
appCaches . IsolatedCaches . ClearCache < PublicAccessEntry > ( ) ;
appCaches . IsolatedCaches . ClearCache < IContent > ( ) ;
}
#endregion
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
#region Define
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
public static readonly Guid UniqueId = Guid . Parse ( "900A4FBE-DF3C-41E6-BB77-BE896CD158EA" ) ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
public override Guid RefresherUniqueId = > UniqueId ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
public override string Name = > "ContentCacheRefresher" ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
#endregion
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
#region Refresher
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
public override void Refresh ( JsonPayload [ ] payloads )
{
AppCaches . RuntimeCache . ClearOfType < PublicAccessEntry > ( ) ;
AppCaches . RuntimeCache . ClearByKey ( CacheKeys . ContentRecycleBinCacheKey ) ;
var idsRemoved = new HashSet < int > ( ) ;
IAppPolicyCache isolatedCache = AppCaches . IsolatedCaches . GetOrCreate < IContent > ( ) ;
foreach ( JsonPayload payload in payloads . Where ( x = > x . Id ! = default ) )
2016-05-26 17:12:04 +02:00
{
2022-06-07 15:28:38 +02:00
// By INT Id
isolatedCache . Clear ( RepositoryCacheKeys . GetKey < IContent , int > ( payload . Id ) ) ;
2016-11-03 10:31:44 +01:00
2022-06-07 15:28:38 +02:00
// By GUID Key
isolatedCache . Clear ( RepositoryCacheKeys . GetKey < IContent , Guid ? > ( payload . Key ) ) ;
2018-05-02 14:52:00 +10:00
2024-09-27 09:12:19 +02:00
2022-06-07 15:28:38 +02:00
// remove those that are in the branch
if ( payload . ChangeTypes . HasTypesAny ( TreeChangeTypes . RefreshBranch | TreeChangeTypes . Remove ) )
2016-05-26 17:12:04 +02:00
{
2022-06-07 15:28:38 +02:00
var pathid = "," + payload . Id + "," ;
isolatedCache . ClearOfType < IContent > ( ( k , v ) = > v . Path ? . Contains ( pathid ) ? ? false ) ;
2018-05-02 14:52:00 +10:00
}
2024-02-01 09:55:09 +01:00
// if the item is not a blueprint and is being completely removed, we need to refresh the domains cache if any domain was assigned to the content
if ( payload . Blueprint is false & & payload . ChangeTypes . HasTypesAny ( TreeChangeTypes . Remove ) )
2018-05-02 14:52:00 +10:00
{
2022-06-07 15:28:38 +02:00
idsRemoved . Add ( payload . Id ) ;
2016-05-26 17:12:04 +02:00
}
2024-09-27 09:12:19 +02:00
2024-10-28 15:31:39 +01:00
HandleMemoryCache ( payload ) ;
2024-09-27 09:12:19 +02:00
HandleRouting ( payload ) ;
2024-09-30 16:43:05 +02:00
HandleNavigation ( payload ) ;
2024-10-15 19:33:23 +02:00
HandlePublishedAsync ( payload , CancellationToken . None ) . GetAwaiter ( ) . GetResult ( ) ;
2024-09-27 09:12:19 +02:00
_idKeyMap . ClearCache ( payload . Id ) ;
if ( payload . Key . HasValue )
{
_idKeyMap . ClearCache ( payload . Key . Value ) ;
}
2016-05-26 17:12:04 +02:00
}
2022-06-07 15:28:38 +02:00
if ( idsRemoved . Count > 0 )
{
var assignedDomains = _domainService . GetAll ( true )
? . Where ( x = > x . RootContentId . HasValue & & idsRemoved . Contains ( x . RootContentId . Value ) ) . ToList ( ) ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
if ( assignedDomains ? . Count > 0 )
{
// TODO: this is duplicating the logic in DomainCacheRefresher BUT we cannot inject that into this because it it not registered explicitly in the container,
// and we cannot inject the CacheRefresherCollection since that would be a circular reference, so what is the best way to call directly in to the
// DomainCacheRefresher?
ClearAllIsolatedCacheByEntityType < IDomain > ( ) ;
// note: must do what's above FIRST else the repositories still have the old cached
// content and when the PublishedCachesService is notified of changes it does not see
// the new content...
// notify
2024-10-01 15:03:02 +02:00
_domainCacheService . Refresh ( assignedDomains
2022-06-07 15:28:38 +02:00
. Select ( x = > new DomainCacheRefresher . JsonPayload ( x . Id , DomainChangeTypes . Remove ) ) . ToArray ( ) ) ;
}
}
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
base . Refresh ( payloads ) ;
}
2016-05-26 17:12:04 +02:00
2024-10-28 15:31:39 +01:00
private void HandleMemoryCache ( JsonPayload payload )
{
Guid key = payload . Key ? ? _idKeyMap . GetKeyForId ( payload . Id , UmbracoObjectTypes . Document ) . Result ;
if ( payload . Blueprint )
{
return ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshNode ) )
{
_documentCacheService . RefreshMemoryCacheAsync ( key ) . GetAwaiter ( ) . GetResult ( ) ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshBranch ) )
{
if ( _documentNavigationQueryService . TryGetDescendantsKeys ( key , out IEnumerable < Guid > descendantsKeys ) )
{
var branchKeys = descendantsKeys . ToList ( ) ;
branchKeys . Add ( key ) ;
foreach ( Guid branchKey in branchKeys )
{
_documentCacheService . RefreshMemoryCacheAsync ( branchKey ) . GetAwaiter ( ) . GetResult ( ) ;
}
}
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshAll ) )
{
_documentCacheService . ClearMemoryCacheAsync ( CancellationToken . None ) . GetAwaiter ( ) . GetResult ( ) ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . Remove ) )
{
_documentCacheService . RemoveFromMemoryCacheAsync ( key ) . GetAwaiter ( ) . GetResult ( ) ;
}
}
2024-09-30 16:43:05 +02:00
private void HandleNavigation ( JsonPayload payload )
{
2024-10-15 19:33:23 +02:00
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshAll ) )
{
_documentNavigationManagementService . RebuildAsync ( ) ;
_documentNavigationManagementService . RebuildBinAsync ( ) ;
}
2024-09-30 16:43:05 +02:00
if ( payload . Key is null )
{
return ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . Remove ) )
{
_documentNavigationManagementService . MoveToBin ( payload . Key . Value ) ;
_documentNavigationManagementService . RemoveFromBin ( payload . Key . Value ) ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshNode ) )
{
2024-10-15 19:33:23 +02:00
IContent ? content = _contentService . GetById ( payload . Key . Value ) ;
2024-09-30 16:43:05 +02:00
if ( content is null )
{
return ;
}
HandleNavigationForSingleContent ( content ) ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshBranch ) )
{
2024-10-15 19:33:23 +02:00
IContent ? content = _contentService . GetById ( payload . Key . Value ) ;
2024-09-30 16:43:05 +02:00
if ( content is null )
{
return ;
}
IEnumerable < IContent > descendants = _contentService . GetPagedDescendants ( content . Id , 0 , int . MaxValue , out _ ) ;
foreach ( IContent descendant in content . Yield ( ) . Concat ( descendants ) )
{
HandleNavigationForSingleContent ( descendant ) ;
}
}
}
private void HandleNavigationForSingleContent ( IContent content )
{
// First creation
if ( ExistsInNavigation ( content . Key ) is false & & ExistsInNavigationBin ( content . Key ) is false )
{
2024-10-16 10:51:42 +03:00
_documentNavigationManagementService . Add ( content . Key , GetParentKey ( content ) , content . SortOrder ) ;
2024-09-30 16:43:05 +02:00
if ( content . Trashed )
{
// If created as trashed, move to bin
_documentNavigationManagementService . MoveToBin ( content . Key ) ;
}
}
else if ( ExistsInNavigation ( content . Key ) & & ExistsInNavigationBin ( content . Key ) is false )
{
if ( content . Trashed )
{
// It must have been trashed
_documentNavigationManagementService . MoveToBin ( content . Key ) ;
}
else
{
2024-10-16 10:51:42 +03:00
if ( _documentNavigationQueryService . TryGetParentKey ( content . Key , out Guid ? oldParentKey ) is false )
{
return ;
}
2024-09-30 16:43:05 +02:00
// It must have been saved. Check if parent is different
2024-10-16 10:51:42 +03:00
Guid ? newParentKey = GetParentKey ( content ) ;
if ( oldParentKey ! = newParentKey )
{
_documentNavigationManagementService . Move ( content . Key , newParentKey ) ;
}
else
2024-09-30 16:43:05 +02:00
{
2024-10-16 10:51:42 +03:00
_documentNavigationManagementService . UpdateSortOrder ( content . Key , content . SortOrder ) ;
2024-09-30 16:43:05 +02:00
}
}
}
else if ( ExistsInNavigation ( content . Key ) is false & & ExistsInNavigationBin ( content . Key ) )
{
if ( content . Trashed is false )
{
// It must have been restored
_documentNavigationManagementService . RestoreFromBin ( content . Key , GetParentKey ( content ) ) ;
}
}
}
private Guid ? GetParentKey ( IContent content ) = > ( content . ParentId = = - 1 ) ? null : _idKeyMap . GetKeyForId ( content . ParentId , UmbracoObjectTypes . Document ) . Result ;
private bool ExistsInNavigation ( Guid contentKey ) = > _documentNavigationQueryService . TryGetParentKey ( contentKey , out _ ) ;
private bool ExistsInNavigationBin ( Guid contentKey ) = > _documentNavigationQueryService . TryGetParentKeyInBin ( contentKey , out _ ) ;
2024-10-15 19:33:23 +02:00
private async Task HandlePublishedAsync ( JsonPayload payload , CancellationToken cancellationToken )
{
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshAll ) )
{
await _publishStatusManagementService . InitializeAsync ( cancellationToken ) ;
}
if ( payload . Key . HasValue is false )
{
return ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . Remove ) )
{
await _publishStatusManagementService . RemoveAsync ( payload . Key . Value , cancellationToken ) ;
}
else if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshNode ) )
{
await _publishStatusManagementService . AddOrUpdateStatusAsync ( payload . Key . Value , cancellationToken ) ;
}
else if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshBranch ) )
{
await _publishStatusManagementService . AddOrUpdateStatusWithDescendantsAsync ( payload . Key . Value , cancellationToken ) ;
}
}
2024-09-27 09:12:19 +02:00
private void HandleRouting ( JsonPayload payload )
{
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . Remove ) )
{
var key = payload . Key ? ? _idKeyMap . GetKeyForId ( payload . Id , UmbracoObjectTypes . Document ) . Result ;
//Note the we need to clear the navigation service as the last thing
if ( _documentNavigationQueryService . TryGetDescendantsKeysOrSelfKeys ( key , out var descendantsOrSelfKeys ) )
{
_documentUrlService . DeleteUrlsFromCacheAsync ( descendantsOrSelfKeys ) . GetAwaiter ( ) . GetResult ( ) ;
} else if ( _documentNavigationQueryService . TryGetDescendantsKeysOrSelfKeysInBin ( key , out var descendantsOrSelfKeysInBin ) )
{
_documentUrlService . DeleteUrlsFromCacheAsync ( descendantsOrSelfKeysInBin ) . GetAwaiter ( ) . GetResult ( ) ;
}
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshAll ) )
{
_documentUrlService . RebuildAllUrlsAsync ( ) . GetAwaiter ( ) . GetResult ( ) ; //TODO make async
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshNode ) )
{
var key = payload . Key ? ? _idKeyMap . GetKeyForId ( payload . Id , UmbracoObjectTypes . Document ) . Result ;
_documentUrlService . CreateOrUpdateUrlSegmentsAsync ( key ) . GetAwaiter ( ) . GetResult ( ) ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshBranch ) )
{
var key = payload . Key ? ? _idKeyMap . GetKeyForId ( payload . Id , UmbracoObjectTypes . Document ) . Result ;
_documentUrlService . CreateOrUpdateUrlSegmentsWithDescendantsAsync ( key ) . GetAwaiter ( ) . GetResult ( ) ;
}
}
2022-06-07 15:28:38 +02:00
// these events should never trigger
// everything should be PAYLOAD/JSON
public override void RefreshAll ( ) = > throw new NotSupportedException ( ) ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
public override void Refresh ( int id ) = > throw new NotSupportedException ( ) ;
2019-10-14 15:21:00 +11:00
2022-06-07 15:28:38 +02:00
public override void Refresh ( Guid id ) = > throw new NotSupportedException ( ) ;
2019-10-14 15:21:00 +11:00
2022-06-07 15:28:38 +02:00
public override void Remove ( int id ) = > throw new NotSupportedException ( ) ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
#endregion
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
#region Json
2016-05-26 17:12:04 +02:00
2024-02-01 09:55:09 +01:00
// TODO (V14): Change into a record
2022-06-07 15:28:38 +02:00
public class JsonPayload
{
2016-05-26 17:12:04 +02:00
2024-02-01 09:55:09 +01:00
public int Id { get ; init ; }
public Guid ? Key { get ; init ; }
2016-05-26 17:12:04 +02:00
2024-02-01 09:55:09 +01:00
public TreeChangeTypes ChangeTypes { get ; init ; }
2022-06-07 15:28:38 +02:00
2024-02-01 09:55:09 +01:00
public bool Blueprint { get ; init ; }
2016-05-26 17:12:04 +02:00
}
2022-06-07 15:28:38 +02:00
#endregion
2016-05-26 17:12:04 +02:00
}