From 375738df9385d3c0bc02699254e5b14f838e37fa Mon Sep 17 00:00:00 2001 From: Daniel Stolt Date: Wed, 28 Aug 2013 05:29:37 +0200 Subject: [PATCH] Added support for Windows Azure Shared Cache to Orchard.Azure.DatabaseCache. --- .../Caching/Database/AzureCacheClient.cs | 52 ++++++++++--------- .../Database/AzureCacheConfiguration.cs | 7 ++- .../Caching/Database/AzureCacheProvider.cs | 52 ++++++++++++++++++- 3 files changed, 82 insertions(+), 29 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Azure/Services/Caching/Database/AzureCacheClient.cs b/src/Orchard.Web/Modules/Orchard.Azure/Services/Caching/Database/AzureCacheClient.cs index 3519a4715..8efa1a1f6 100644 --- a/src/Orchard.Web/Modules/Orchard.Azure/Services/Caching/Database/AzureCacheClient.cs +++ b/src/Orchard.Web/Modules/Orchard.Azure/Services/Caching/Database/AzureCacheClient.cs @@ -9,49 +9,36 @@ namespace Orchard.Azure.Services.Caching.Database { public class AzureCacheClient : ICache { - public AzureCacheClient(string cacheHostIdentifier, string cacheName, string region, bool enableCompression, TimeSpan? expirationTime) { - - var dataCacheFactoryConfiguration = new DataCacheFactoryConfiguration() { - AutoDiscoverProperty = new DataCacheAutoDiscoverProperty(true, cacheHostIdentifier), - MaxConnectionsToServer = 32, - UseLegacyProtocol = false, - IsCompressionEnabled = enableCompression - }; - - var dataCacheFactory = new DataCacheFactory(dataCacheFactoryConfiguration); - + public AzureCacheClient(DataCache cache, bool isSharedCaching, string region, TimeSpan? expirationTime) { _logger = LoggerProvider.LoggerFor(typeof(AzureCacheClient)); + _cache = cache; + _isSharedCaching = isSharedCaching; _region = region ?? _defaultRegion; - // Azure Cache supports only alphanumeric strings for regions and // Orchard can get a lot more creative than that. Remove all non // alphanumering characters from the region, and append the hash code // of the original string to mitigate the risk of two distinct original // region strings yielding the same transformed region string. _regionAlphaNumeric = new String(Array.FindAll(_region.ToCharArray(), c => Char.IsLetterOrDigit(c))) + _region.GetHashCode().ToString(); - _expirationTime = expirationTime; - - if (_logger.IsDebugEnabled) - _logger.DebugFormat("Creating cache with CacheName='{0}' and Region='{1}' (original Region='{2}').", cacheName, _regionAlphaNumeric, _region); - - if (!String.IsNullOrEmpty(cacheName)) - _cache = dataCacheFactory.GetCache(cacheName); - else - _cache = dataCacheFactory.GetDefaultCache(); - - _cache.CreateRegion(_regionAlphaNumeric); + + if (!isSharedCaching) + _cache.CreateRegion(_regionAlphaNumeric); //_lockHandleDictionary = new ConcurrentDictionary(); //_lockTimeout = TimeSpan.FromSeconds(30); + + if (_logger.IsDebugEnabled) + _logger.DebugFormat("Created an AzureCacheClient for region '{0}' (original region '{1}').", _regionAlphaNumeric, _region); } private const string _defaultRegion = "NHibernate"; private readonly IInternalLogger _logger; + private readonly DataCache _cache; + private readonly bool _isSharedCaching; private readonly string _region; private readonly string _regionAlphaNumeric; private readonly TimeSpan? _expirationTime; - private readonly DataCache _cache; //private readonly ConcurrentDictionary _lockHandleDictionary; //private readonly TimeSpan _lockTimeout; @@ -64,6 +51,9 @@ namespace Orchard.Azure.Services.Caching.Database { if (_logger.IsDebugEnabled) _logger.DebugFormat("Get() invoked with key='{0}' in region '{1}'.", key, _regionAlphaNumeric); + if (_isSharedCaching) + return _cache.Get(GetSharedCachingKey(key)); + return _cache.Get(key.ToString(), _regionAlphaNumeric); } @@ -76,7 +66,9 @@ namespace Orchard.Azure.Services.Caching.Database { if (_logger.IsDebugEnabled) _logger.DebugFormat("Put() invoked with key='{0}' and value='{1}' in region '{2}'.", key, value, _regionAlphaNumeric); - if (_expirationTime.HasValue) + if (_isSharedCaching) + _cache.Put(GetSharedCachingKey(key), value); + else if (_expirationTime.HasValue) _cache.Put(key.ToString(), value, _expirationTime.Value, _regionAlphaNumeric); else _cache.Put(key.ToString(), value, _regionAlphaNumeric); @@ -89,12 +81,18 @@ namespace Orchard.Azure.Services.Caching.Database { if (_logger.IsDebugEnabled) _logger.DebugFormat("Remove() invoked with key='{0}' in region '{1}'.", key, _regionAlphaNumeric); + if (_isSharedCaching) + _cache.Remove(key.ToString()); + _cache.Remove(key.ToString(), _regionAlphaNumeric); } public void Clear() { if (_logger.IsDebugEnabled) _logger.DebugFormat("Clear() invoked in region '{0}'.", _regionAlphaNumeric); + + if (_isSharedCaching) + return; // Can't remove an individual region with Shared Caching. _cache.ClearRegion(_regionAlphaNumeric); } @@ -184,5 +182,9 @@ namespace Orchard.Azure.Services.Caching.Database { } #endregion + + private string GetSharedCachingKey(object key) { + return String.Format("{0}_{1}", _region, key); + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure/Services/Caching/Database/AzureCacheConfiguration.cs b/src/Orchard.Web/Modules/Orchard.Azure/Services/Caching/Database/AzureCacheConfiguration.cs index 7215e19b0..aae4fc582 100644 --- a/src/Orchard.Web/Modules/Orchard.Azure/Services/Caching/Database/AzureCacheConfiguration.cs +++ b/src/Orchard.Web/Modules/Orchard.Azure/Services/Caching/Database/AzureCacheConfiguration.cs @@ -1,10 +1,11 @@ -using NHibernate.Cfg.Loquacious; +using System; +using System.Linq; +using NHibernate.Cfg.Loquacious; using Orchard; using Orchard.Data; using Orchard.Environment.Configuration; using Orchard.Environment.Extensions; using Orchard.Logging; -using System.Linq; namespace Orchard.Azure.Services.Caching.Database { @@ -14,6 +15,7 @@ namespace Orchard.Azure.Services.Caching.Database { public static string CacheHostIdentifier; public static string CacheName; + public static bool IsSharedCaching; public AzureCacheConfiguration(IShellSettingsManager shellSettingsManager, ShellSettings shellSettings) : base() { @@ -40,6 +42,7 @@ namespace Orchard.Azure.Services.Caching.Database { CacheHostIdentifier = shellSettings[Constants.DatabaseCacheHostIdentifierSettingName]; CacheName = shellSettings[Constants.DatabaseCacheCacheNameSettingName]; + IsSharedCaching = Boolean.Parse(shellSettings[Constants.DatabaseCacheIsSharedCachingSettingName]); _shellSettings = shellSettings; } diff --git a/src/Orchard.Web/Modules/Orchard.Azure/Services/Caching/Database/AzureCacheProvider.cs b/src/Orchard.Web/Modules/Orchard.Azure/Services/Caching/Database/AzureCacheProvider.cs index 244429b8f..6bc04ca56 100644 --- a/src/Orchard.Web/Modules/Orchard.Azure/Services/Caching/Database/AzureCacheProvider.cs +++ b/src/Orchard.Web/Modules/Orchard.Azure/Services/Caching/Database/AzureCacheProvider.cs @@ -2,11 +2,54 @@ using System.Collections.Generic; using Microsoft.WindowsAzure.ServiceRuntime; using NHibernate.Cache; using System; +using Microsoft.ApplicationServer.Caching; +using NHibernate; namespace Orchard.Azure.Services.Caching.Database { public class AzureCacheProvider : ICacheProvider { + #region DataCache repository + + private static IDictionary _cacheDictionary = new Dictionary(); + + private static DataCache GetCache(IInternalLogger logger, string cacheHostIdentifier, string cacheName, bool enableCompression) { + string key = String.Format("{0}_{1}_{2}", cacheHostIdentifier, cacheName, enableCompression); + if (!_cacheDictionary.ContainsKey(key)) { + var dataCacheFactoryConfiguration = new DataCacheFactoryConfiguration() { + AutoDiscoverProperty = new DataCacheAutoDiscoverProperty(true, AzureCacheConfiguration.CacheHostIdentifier), + MaxConnectionsToServer = 32, + UseLegacyProtocol = false + }; + + var dataCacheFactory = new DataCacheFactory(dataCacheFactoryConfiguration); + + if (logger.IsDebugEnabled) + logger.DebugFormat("Creating DataCache with CacheHostIdentifier='{0}' and CacheName='{1}'.", cacheHostIdentifier, cacheName); + + DataCache newCache; + if (!String.IsNullOrEmpty(cacheName)) + newCache = dataCacheFactory.GetCache(cacheName); + else + newCache = dataCacheFactory.GetDefaultCache(); + _cacheDictionary[key] = newCache; + } + else { + if (logger.IsDebugEnabled) + logger.DebugFormat("Reusing existing DataCache with CacheHostIdentifier='{0}' and CacheName='{1}'.", cacheHostIdentifier, cacheName); + } + + return _cacheDictionary[key]; + } + + #endregion + + public AzureCacheProvider() { + _logger = LoggerProvider.LoggerFor(typeof(AzureCacheProvider)); + } + + private readonly IInternalLogger _logger; + #region ICacheProvider Members public ICache BuildCache(string regionName, IDictionary properties) { @@ -15,12 +58,17 @@ namespace Orchard.Azure.Services.Caching.Database { if (properties.TryGetValue("compression_enabled", out enableCompressionString)) enableCompression = Boolean.Parse(enableCompressionString); + // Using static fields to communicate host identifier and cache name from AzureCacheConfiguration to + // this class might cause problems in multi-tenancy scenarios when tenants have different settings + // for these in shell settings. We should think of something more robust. + var cache = GetCache(_logger, AzureCacheConfiguration.CacheHostIdentifier, AzureCacheConfiguration.CacheName, enableCompression); + TimeSpan? expiration = null; string expirationString; if (properties.TryGetValue("expiration", out expirationString) || properties.TryGetValue(global::NHibernate.Cfg.Environment.CacheDefaultExpiration, out expirationString)) expiration = TimeSpan.FromSeconds(Int32.Parse(expirationString)); - - return new AzureCacheClient(AzureCacheConfiguration.CacheHostIdentifier, AzureCacheConfiguration.CacheName, regionName, enableCompression, expiration); + + return new AzureCacheClient(cache, AzureCacheConfiguration.IsSharedCaching, regionName, expiration); } public long NextTimestamp() {