// (c) Copyright Ascensio System SIA 2010-2022 // // This program is a free software product. // You can redistribute it and/or modify it under the terms // of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software // Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended // to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of // any third-party rights. // // This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see // the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html // // You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. // // The interactive user interfaces in modified source and object code versions of the Program must // display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. // // Pursuant to Section 7(b) of the License you must retain the original Product logo when // distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under // trademark law for use of our trademarks. // // All the Product's GUI elements, including illustrations and icon sets, as well as technical writing // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode namespace ASC.Core.Caching; [Singletone] class TenantServiceCache { private const string _key = "tenants"; private readonly TimeSpan _cacheExpiration; internal readonly ICache _cache; internal readonly ICacheNotify _cacheNotifyItem; internal readonly ICacheNotify _cacheNotifySettings; public TenantServiceCache( CoreBaseSettings coreBaseSettings, ICacheNotify cacheNotifyItem, ICacheNotify cacheNotifySettings, ICache cache) { _cacheNotifyItem = cacheNotifyItem; _cacheNotifySettings = cacheNotifySettings; _cache = cache; _cacheExpiration = TimeSpan.FromMinutes(2); cacheNotifyItem.Subscribe((t) => { var tenants = GetTenantStore(); tenants.Remove(t.TenantId); tenants.Clear(coreBaseSettings); }, CacheNotifyAction.InsertOrUpdate); cacheNotifySettings.Subscribe((s) => { _cache.Remove(s.Key); }, CacheNotifyAction.Remove); } internal TenantStore GetTenantStore() { var store = _cache.Get(_key); if (store == null) { store = new TenantStore(); _cache.Insert(_key, store, DateTime.UtcNow.Add(_cacheExpiration)); } return store; } internal class TenantStore { private readonly Dictionary _byId = new Dictionary(); private readonly Dictionary _byDomain = new Dictionary(); private readonly object _locker = new object(); public Tenant Get(int id) { Tenant t; lock (_locker) { _byId.TryGetValue(id, out t); } return t; } public Tenant Get(string domain) { if (string.IsNullOrEmpty(domain)) { return null; } Tenant t; lock (_locker) { _byDomain.TryGetValue(domain, out t); } return t; } public void Insert(Tenant t, string ip = null) { if (t == null) { return; } Remove(t.Id); lock (_locker) { _byId[t.Id] = t; _byDomain[t.Alias] = t; if (!string.IsNullOrEmpty(t.MappedDomain)) { _byDomain[t.MappedDomain] = t; } if (!string.IsNullOrEmpty(ip)) { _byDomain[ip] = t; } } } public void Remove(int id) { var t = Get(id); if (t != null) { lock (_locker) { _byId.Remove(id); _byDomain.Remove(t.Alias); if (!string.IsNullOrEmpty(t.MappedDomain)) { _byDomain.Remove(t.MappedDomain); } } } } internal void Clear(CoreBaseSettings coreBaseSettings) { if (!coreBaseSettings.Standalone) { return; } lock (_locker) { _byId.Clear(); _byDomain.Clear(); } } } } [Scope] class ConfigureCachedTenantService : IConfigureNamedOptions { private readonly IOptionsSnapshot _service; private readonly TenantServiceCache _tenantServiceCache; public ConfigureCachedTenantService( IOptionsSnapshot service, TenantServiceCache tenantServiceCache) { _service = service; _tenantServiceCache = tenantServiceCache; } public void Configure(string name, CachedTenantService options) { Configure(options); options._service = _service.Get(name); } public void Configure(CachedTenantService options) { options._service = _service.Value; options._tenantServiceCache = _tenantServiceCache; options._cacheNotifyItem = _tenantServiceCache._cacheNotifyItem; options._cacheNotifySettings = _tenantServiceCache._cacheNotifySettings; } } [Scope] class CachedTenantService : ITenantService { internal ITenantService _service; private readonly ICache _cache; internal ICacheNotify _cacheNotifySettings; internal ICacheNotify _cacheNotifyItem; private readonly TimeSpan _settingsExpiration; internal TenantServiceCache _tenantServiceCache; public CachedTenantService() { _settingsExpiration = TimeSpan.FromMinutes(2); } public CachedTenantService(DbTenantService service, TenantServiceCache tenantServiceCache, ICache cache) : this() { this._cache = cache; _service = service ?? throw new ArgumentNullException(nameof(service)); _tenantServiceCache = tenantServiceCache; _cacheNotifyItem = tenantServiceCache._cacheNotifyItem; _cacheNotifySettings = tenantServiceCache._cacheNotifySettings; } public void ValidateDomain(string domain) { _service.ValidateDomain(domain); } public IEnumerable GetTenants(string login, string passwordHash) { return _service.GetTenants(login, passwordHash); } public IEnumerable GetTenants(DateTime from, bool active = true) { return _service.GetTenants(from, active); } public IEnumerable GetTenants(List ids) { return _service.GetTenants(ids); } public Tenant GetTenant(int id) { var tenants = _tenantServiceCache.GetTenantStore(); var t = tenants.Get(id); if (t == null) { t = _service.GetTenant(id); if (t != null) { tenants.Insert(t); } } return t; } public Tenant GetTenant(string domain) { var tenants = _tenantServiceCache.GetTenantStore(); var t = tenants.Get(domain); if (t == null) { t = _service.GetTenant(domain); if (t != null) { tenants.Insert(t); } } return t; } public Tenant GetTenantForStandaloneWithoutAlias(string ip) { var tenants = _tenantServiceCache.GetTenantStore(); var t = tenants.Get(ip); if (t == null) { t = _service.GetTenantForStandaloneWithoutAlias(ip); if (t != null) { tenants.Insert(t, ip); } } return t; } public Tenant SaveTenant(CoreSettings coreSettings, Tenant tenant) { tenant = _service.SaveTenant(coreSettings, tenant); _cacheNotifyItem.Publish(new TenantCacheItem() { TenantId = tenant.Id }, CacheNotifyAction.InsertOrUpdate); return tenant; } public void RemoveTenant(int id, bool auto = false) { _service.RemoveTenant(id, auto); _cacheNotifyItem.Publish(new TenantCacheItem() { TenantId = id }, CacheNotifyAction.InsertOrUpdate); } public IEnumerable GetTenantVersions() { return _service.GetTenantVersions(); } public byte[] GetTenantSettings(int tenant, string key) { var cacheKey = string.Format("settings/{0}/{1}", tenant, key); var data = _cache.Get(cacheKey); if (data == null) { data = _service.GetTenantSettings(tenant, key); _cache.Insert(cacheKey, data ?? Array.Empty(), DateTime.UtcNow + _settingsExpiration); } return data == null ? null : data.Length == 0 ? null : data; } public void SetTenantSettings(int tenant, string key, byte[] data) { _service.SetTenantSettings(tenant, key, data); var cacheKey = string.Format("settings/{0}/{1}", tenant, key); _cacheNotifySettings.Publish(new TenantSetting { Key = cacheKey }, ASC.Common.Caching.CacheNotifyAction.Remove); } }