diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStore.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStore.cs index 966fca75ae..20d95da692 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStore.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStore.cs @@ -1,8 +1,11 @@ -using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Options; using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; namespace Volo.Abp.PermissionManagement; @@ -12,35 +15,72 @@ public class DynamicPermissionDefinitionStore : IDynamicPermissionDefinitionStor protected IPermissionGroupDefinitionRecordRepository PermissionGroupRepository { get; } protected IPermissionDefinitionRecordRepository PermissionRepository { get; } protected IPermissionDefinitionSerializer PermissionDefinitionSerializer { get; } - + protected IDynamicPermissionDefinitionStoreInMemoryCache StoreCache { get; } + protected IDistributedCache DistributedCache { get; } + protected AbpDistributedCacheOptions CacheOptions { get; } + public DynamicPermissionDefinitionStore( IPermissionGroupDefinitionRecordRepository permissionGroupRepository, IPermissionDefinitionRecordRepository permissionRepository, - IPermissionDefinitionSerializer permissionDefinitionSerializer) + IPermissionDefinitionSerializer permissionDefinitionSerializer, + IDynamicPermissionDefinitionStoreInMemoryCache storeCache, + IDistributedCache distributedCache, + IOptions cacheOptions) { PermissionGroupRepository = permissionGroupRepository; PermissionRepository = permissionRepository; PermissionDefinitionSerializer = permissionDefinitionSerializer; + StoreCache = storeCache; + DistributedCache = distributedCache; + CacheOptions = cacheOptions.Value; } public virtual async Task GetOrNullAsync(string name) { - var permissionRecord = await PermissionRepository.FindByNameAsync(name); - if (permissionRecord == null) - { - return null; - } - - throw new NotImplementedException(); + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetPermissionOrNull(name); } public virtual async Task> GetPermissionsAsync() { - throw new NotImplementedException(); + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetPermissions(); } public virtual async Task> GetGroupsAsync() { - throw new NotImplementedException(); + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetGroups(); + } + + private async Task EnsureCacheIsUptoDateAsync() + { + /* TODO: Optimization note: May not check for a few seconds + * It is acceptable to get changes a few seconds after the last time the cache was updated. + */ + + using (await StoreCache.SyncSemaphore.LockAsync()) + { + var cacheKey = GetCacheKey(); + + var stampInDistributedCache = await DistributedCache.GetStringAsync(cacheKey); + + if (StoreCache.CacheStamp == stampInDistributedCache) + { + return; + } + + var permissionGroupRecords = await PermissionGroupRepository.GetListAsync(); + var permissionRecords = await PermissionRepository.GetListAsync(); + + await StoreCache.FillAsync(permissionGroupRecords, permissionRecords); + + StoreCache.CacheStamp = stampInDistributedCache; + } + } + + private string GetCacheKey() + { + return $"{CacheOptions.KeyPrefix}_AbpInMemoryPermissionCacheStamp"; } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStoreCache.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStoreInMemoryCache.cs similarity index 73% rename from modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStoreCache.cs rename to modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStoreInMemoryCache.cs index 002121aeb3..d807de9371 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStoreCache.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/DynamicPermissionDefinitionStoreInMemoryCache.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Authorization.Permissions; using Volo.Abp.DependencyInjection; @@ -9,27 +10,32 @@ using Volo.Abp.SimpleStateChecking; namespace Volo.Abp.PermissionManagement; -//TODO: Extract interface -public class DynamicPermissionDefinitionStoreCache : ISingletonDependency +public class DynamicPermissionDefinitionStoreInMemoryCache : + IDynamicPermissionDefinitionStoreInMemoryCache, + ISingletonDependency { public string CacheStamp { get; set; } protected IDictionary PermissionGroupDefinitions { get; } - protected IDictionary PermissionDefinitions { get; } protected ISimpleStateCheckerSerializer StateCheckerSerializer { get; } - public DynamicPermissionDefinitionStoreCache(ISimpleStateCheckerSerializer stateCheckerSerializer) + public DynamicPermissionDefinitionStoreInMemoryCache(ISimpleStateCheckerSerializer stateCheckerSerializer) { StateCheckerSerializer = stateCheckerSerializer; PermissionGroupDefinitions = new Dictionary(); PermissionDefinitions = new Dictionary(); } + public SemaphoreSlim SyncSemaphore { get; } = new(1, 1); + public Task FillAsync( List permissionGroupRecords, List permissionRecords) { + PermissionGroupDefinitions.Clear(); + PermissionDefinitions.Clear(); + var context = new PermissionDefinitionContext(null); foreach (var permissionGroupRecord in permissionGroupRecords) @@ -38,6 +44,8 @@ public class DynamicPermissionDefinitionStoreCache : ISingletonDependency permissionGroupRecord.Name, new FixedLocalizableString(permissionGroupRecord.DisplayName) //TODO: Consider localization ); + + PermissionGroupDefinitions[permissionGroup.Name] = permissionGroup; foreach (var property in permissionGroupRecord.ExtraProperties) { @@ -52,10 +60,25 @@ public class DynamicPermissionDefinitionStoreCache : ISingletonDependency AddPermissionRecursively(permissionGroup, permissionRecord, permissionRecords); } } - + return Task.CompletedTask; } + public PermissionDefinition GetPermissionOrNull(string name) + { + return PermissionDefinitions.GetOrDefault(name); + } + + public IReadOnlyList GetPermissions() + { + return PermissionDefinitions.Values.ToList(); + } + + public IReadOnlyList GetGroups() + { + return PermissionGroupDefinitions.Values.ToList(); + } + private void AddPermissionRecursively(ICanAddChildPermission permissionContainer, PermissionDefinitionRecord permissionRecord, List allPermissionRecords) @@ -66,6 +89,8 @@ public class DynamicPermissionDefinitionStoreCache : ISingletonDependency permissionRecord.MultiTenancySide, permissionRecord.IsEnabled ); + + PermissionDefinitions[permission.Name] = permission; if (!permissionRecord.Providers.IsNullOrWhiteSpace()) { @@ -91,36 +116,4 @@ public class DynamicPermissionDefinitionStoreCache : ISingletonDependency AddPermissionRecursively(permission, subPermission, allPermissionRecords); } } - - protected virtual Dictionary CreatePermissionDefinitions() - { - var permissions = new Dictionary(); - - foreach (var groupDefinition in PermissionGroupDefinitions.Values) - { - foreach (var permission in groupDefinition.Permissions) - { - AddPermissionToDictionaryRecursively(permissions, permission); - } - } - - return permissions; - } - - protected virtual void AddPermissionToDictionaryRecursively( - Dictionary permissions, - PermissionDefinition permission) - { - if (permissions.ContainsKey(permission.Name)) - { - throw new AbpException("Duplicate permission name: " + permission.Name); - } - - permissions[permission.Name] = permission; - - foreach (var child in permission.Children) - { - AddPermissionToDictionaryRecursively(permissions, child); - } - } } \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IDynamicPermissionDefinitionStoreInMemoryCache.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IDynamicPermissionDefinitionStoreInMemoryCache.cs new file mode 100644 index 0000000000..e6a1da3b79 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/IDynamicPermissionDefinitionStoreInMemoryCache.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; + +namespace Volo.Abp.PermissionManagement; + +public interface IDynamicPermissionDefinitionStoreInMemoryCache +{ + string CacheStamp { get; set; } + + SemaphoreSlim SyncSemaphore { get; } + + Task FillAsync( + List permissionGroupRecords, + List permissionRecords); + + PermissionDefinition GetPermissionOrNull(string name); + + IReadOnlyList GetPermissions(); + + IReadOnlyList GetGroups(); +} \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs index a7e3497af6..8293bdc071 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs @@ -22,8 +22,8 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc protected IPermissionDefinitionSerializer PermissionSerializer { get; } protected IDistributedCache Cache { get; } protected IApplicationNameAccessor ApplicationNameAccessor { get; } - public IAbpDistributedLock DistributedLock { get; } - public PermissionManagementOptions PermissionManagementOptions { get; } + protected IAbpDistributedLock DistributedLock { get; } + protected PermissionManagementOptions PermissionManagementOptions { get; } protected AbpDistributedCacheOptions CacheOptions { get; } public StaticPermissionSaver( @@ -51,6 +51,8 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc [UnitOfWork] public virtual async Task SaveAsync() { + /* TODO: We may double check and lock for optimization + */ await using var handle = await DistributedLock.TryAcquireAsync(GetDistributedLockKey()); if (handle == null) @@ -64,7 +66,7 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc * But the code would be more complex. */ - var cacheKey = GetCacheKey(); + var cacheKey = GetApplicationHashCacheKey(); var cachedHash = await Cache.GetStringAsync(cacheKey); var (permissionGroupRecords, permissionRecords) = await PermissionSerializer.SerializeAsync( @@ -93,6 +95,14 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc SlidingExpiration = TimeSpan.FromDays(30) //TODO: Make it configurable? } ); + + await Cache.SetStringAsync( + GetCommonStampCacheKey(), + Guid.NewGuid().ToString(), + new DistributedCacheEntryOptions { + SlidingExpiration = TimeSpan.FromDays(30) //TODO: Make it configurable? + } + ); } private async Task UpdateChangedPermissionGroupsAsync(IEnumerable permissionGroupRecords) @@ -204,10 +214,15 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc return $"{ApplicationNameAccessor.ApplicationName}_AbpPermissionUpdateLock"; } - private string GetCacheKey() + private string GetApplicationHashCacheKey() { return $"{CacheOptions.KeyPrefix}_{ApplicationNameAccessor.ApplicationName}_AbpPermissionsHash"; } + + private string GetCommonStampCacheKey() + { + return $"{CacheOptions.KeyPrefix}_AbpInMemoryPermissionCacheStamp"; + } private static string CalculateHash( PermissionGroupDefinitionRecord[] permissionGroupRecords,