From e97a01c75c03be755f7604fcf38e3e34b0feecc5 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sat, 23 Mar 2013 01:59:25 +0600 Subject: [PATCH] Fixes: #U4-1992 - Creates DictionaryCacheRefresher to ensure that all cache associated with the dictionary is updated amongst all servers when it is changed/removed. Removes RemoveByJson as we only actually require RefreshByJson since we can put any sort of parameters in a custom json string including whether it is a remove operation (if required) --- src/Umbraco.Core/Cache/IJsonCacheRefresher.cs | 5 +- .../Cache/JsonCacheRefresherBase.cs | 5 -- .../Sync/DefaultServerMessenger.cs | 29 +------ src/Umbraco.Core/Sync/IServerMessenger.cs | 10 --- src/Umbraco.Core/Sync/MessageType.cs | 1 - .../Sync/ServerSyncWebServiceClient.cs | 29 +------ .../BusinessLogic/DictionaryTest.cs | 1 + .../Cache/CacheRefresherEventHandler.cs | 42 +++++++++- .../Cache/ContentTypeCacheRefresher.cs | 11 --- .../Cache/DictionaryCacheRefresher.cs | 42 ++++++++++ src/Umbraco.Web/Cache/DistributedCache.cs | 17 +--- .../Cache/DistributedCacheExtensions.cs | 36 +++++++-- .../Cache/LanguageCacheRefresher.cs | 4 +- src/Umbraco.Web/Cache/MacroCacheRefresher.cs | 19 ++--- src/Umbraco.Web/Cache/MediaCacheRefresher.cs | 6 -- src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../webservices/CacheRefresher.asmx.cs | 24 ------ src/umbraco.cms/businesslogic/ContentType.cs | 4 +- src/umbraco.cms/businesslogic/Dictionary.cs | 61 ++++++++------ .../language/{Text.cs => Item.cs} | 79 +++++++------------ .../businesslogic/language/Language.cs | 38 ++++----- src/umbraco.cms/umbraco.cms.csproj | 2 +- 22 files changed, 219 insertions(+), 247 deletions(-) create mode 100644 src/Umbraco.Web/Cache/DictionaryCacheRefresher.cs rename src/umbraco.cms/businesslogic/language/{Text.cs => Item.cs} (73%) diff --git a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs index c72c2569a1..006cc4fec0 100644 --- a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs @@ -7,7 +7,10 @@ namespace Umbraco.Core.Cache /// interface IJsonCacheRefresher : ICacheRefresher { + /// + /// Refreshes, clears, etc... any cache based on the information provided in the json + /// + /// void Refresh(string jsonPayload); - void Remove(string jsonPayload); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs index 72c484645b..708e2e1605 100644 --- a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs @@ -16,10 +16,5 @@ namespace Umbraco.Core.Cache { OnCacheUpdated(Instance, new CacheRefresherEventArgs(jsonPayload, MessageType.RefreshByJson)); } - - public virtual void Remove(string jsonPayload) - { - OnCacheUpdated(Instance, new CacheRefresherEventArgs(jsonPayload, MessageType.RemoveByJson)); - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/DefaultServerMessenger.cs b/src/Umbraco.Core/Sync/DefaultServerMessenger.cs index 973bbb055d..0f9e81d7fd 100644 --- a/src/Umbraco.Core/Sync/DefaultServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DefaultServerMessenger.cs @@ -91,15 +91,6 @@ namespace Umbraco.Core.Sync instances); } - public void PerformRemove(IEnumerable servers, ICacheRefresher refresher, string jsonPayload) - { - if (servers == null) throw new ArgumentNullException("servers"); - if (refresher == null) throw new ArgumentNullException("refresher"); - if (jsonPayload == null) throw new ArgumentNullException("jsonPayload"); - - MessageSeversForIdsOrJson(servers, refresher, MessageType.RemoveByJson, jsonPayload: jsonPayload); - } - public void PerformRemove(IEnumerable servers, ICacheRefresher refresher, Func getNumericId, params T[] instances) { if (servers == null) throw new ArgumentNullException("servers"); @@ -283,15 +274,7 @@ namespace Umbraco.Core.Sync } //if we are not, then just invoke the call on the cache refresher - switch (dispatchType) - { - case MessageType.RefreshByJson: - jsonRefresher.Refresh(jsonPayload); - break; - case MessageType.RemoveByJson: - jsonRefresher.Remove(jsonPayload); - break; - } + jsonRefresher.Refresh(jsonPayload); } } } @@ -380,12 +363,7 @@ namespace Umbraco.Core.Sync asyncResultsList.Add( cacheRefresher.BeginRefreshByJson( refresher.UniqueIdentifier, jsonPayload, _login, _password, null, null)); - break; - case MessageType.RemoveByJson: - asyncResultsList.Add( - cacheRefresher.BeginRemoveByJson( - refresher.UniqueIdentifier, jsonPayload, _login, _password, null, null)); - break; + break; case MessageType.RefreshAll: asyncResultsList.Add( cacheRefresher.BeginRefreshAll( @@ -438,9 +416,6 @@ namespace Umbraco.Core.Sync case MessageType.RefreshByJson: cacheRefresher.EndRefreshByJson(t); break; - case MessageType.RemoveByJson: - cacheRefresher.EndRemoveByJson(t); - break; case MessageType.RefreshAll: cacheRefresher.EndRefreshAll(t); break; diff --git a/src/Umbraco.Core/Sync/IServerMessenger.cs b/src/Umbraco.Core/Sync/IServerMessenger.cs index b39a6367db..45e0a92385 100644 --- a/src/Umbraco.Core/Sync/IServerMessenger.cs +++ b/src/Umbraco.Core/Sync/IServerMessenger.cs @@ -40,16 +40,6 @@ namespace Umbraco.Core.Sync /// void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, Func getGuidId, params T[] instances); - /// - /// Performs a remove and sends along the JSON payload to each server - /// - /// - /// - /// - /// A pre-formatted custom json payload to be sent to the servers, the cache refresher will deserialize and use to remove cache - /// - void PerformRemove(IEnumerable servers, ICacheRefresher refresher, string jsonPayload); - /// /// Removes the cache for the specified items /// diff --git a/src/Umbraco.Core/Sync/MessageType.cs b/src/Umbraco.Core/Sync/MessageType.cs index 3c107fba8c..aa8733be33 100644 --- a/src/Umbraco.Core/Sync/MessageType.cs +++ b/src/Umbraco.Core/Sync/MessageType.cs @@ -9,7 +9,6 @@ RefreshById, RefreshByJson, RemoveById, - RemoveByJson, RefreshByInstance, RemoveByInstance } diff --git a/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs b/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs index c45ceace23..dd7d0db01d 100644 --- a/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs +++ b/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs @@ -45,34 +45,7 @@ namespace Umbraco.Core.Sync { this.EndInvoke(asyncResult); } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RemoveByJson", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public void RemoveByJson(System.Guid uniqueIdentifier, string jsonPayload, string Login, string Password) - { - this.Invoke("RemoveByJson", new object[] { - uniqueIdentifier, - jsonPayload, - Login, - Password}); - } - - /// - public System.IAsyncResult BeginRemoveByJson(System.Guid uniqueIdentifier, string jsonPayload, string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("RemoveByJson", new object[] { - uniqueIdentifier, - jsonPayload, - Login, - Password}, callback, asyncState); - } - - /// - public void EndRemoveByJson(System.IAsyncResult asyncResult) - { - this.EndInvoke(asyncResult); - } - + /// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByJson", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] public void RefreshByJson(System.Guid uniqueIdentifier, string jsonPayload, string Login, string Password) diff --git a/src/Umbraco.Tests/BusinessLogic/DictionaryTest.cs b/src/Umbraco.Tests/BusinessLogic/DictionaryTest.cs index ac2376dc0c..92f3d34c60 100644 --- a/src/Umbraco.Tests/BusinessLogic/DictionaryTest.cs +++ b/src/Umbraco.Tests/BusinessLogic/DictionaryTest.cs @@ -238,6 +238,7 @@ namespace Umbraco.Tests.BusinessLogic var newKey = "NEWKEY" + Guid.NewGuid().ToString("N"); d.key = newKey; + d.Save(); Assert.AreNotEqual(oldKey, d.key); Assert.AreEqual(newKey, d.key); diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 3831242ce8..d1ddd03827 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -23,6 +23,15 @@ namespace Umbraco.Web.Cache protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { + //Bind to dictionary events + //NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979 + + global::umbraco.cms.businesslogic.Dictionary.DictionaryItem.New += DictionaryItemNew; + global::umbraco.cms.businesslogic.Dictionary.DictionaryItem.Saving +=DictionaryItemSaving; + global::umbraco.cms.businesslogic.Dictionary.DictionaryItem.Deleted +=DictionaryItemDeleted; + LocalizationService.DeletedDictionaryItem += LocalizationServiceDeletedDictionaryItem; + LocalizationService.SavedDictionaryItem += LocalizationServiceSavedDictionaryItem; + //Bind to data type events //NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979 @@ -97,6 +106,35 @@ namespace Umbraco.Web.Cache MediaService.Trashing += MediaServiceTrashing; } + #region Dictionary event handlers + + static void LocalizationServiceSavedDictionaryItem(ILocalizationService sender, Core.Events.SaveEventArgs e) + { + e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDictionaryCache(x.Id)); + } + + static void LocalizationServiceDeletedDictionaryItem(ILocalizationService sender, Core.Events.DeleteEventArgs e) + { + e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDictionaryCache(x.Id)); + } + + static void DictionaryItemDeleted(global::umbraco.cms.businesslogic.Dictionary.DictionaryItem sender, System.EventArgs e) + { + DistributedCache.Instance.RemoveDictionaryCache(sender.id); + } + + static void DictionaryItemSaving(global::umbraco.cms.businesslogic.Dictionary.DictionaryItem sender, System.EventArgs e) + { + DistributedCache.Instance.RefreshDictionaryCache(sender.id); + } + + static void DictionaryItemNew(global::umbraco.cms.businesslogic.Dictionary.DictionaryItem sender, System.EventArgs e) + { + DistributedCache.Instance.RefreshDictionaryCache(sender.id); + } + + #endregion + #region DataType event handlers static void DataTypeServiceSaved(IDataTypeService sender, Core.Events.SaveEventArgs e) { @@ -132,12 +170,12 @@ namespace Umbraco.Web.Cache static void FileServiceDeletedStylesheet(IFileService sender, Core.Events.DeleteEventArgs e) { - e.DeletedEntities.ForEach(DistributedCache.Instance.RemoveStylesheetCache); + e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveStylesheetCache(x)); } static void FileServiceSavedStylesheet(IFileService sender, Core.Events.SaveEventArgs e) { - e.SavedEntities.ForEach(DistributedCache.Instance.RefreshStylesheetCache); + e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshStylesheetCache(x)); } static void StyleSheetAfterSave(StyleSheet sender, SaveEventArgs e) diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 89698caa4b..0e31bf0bc3 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -156,17 +156,6 @@ namespace Umbraco.Web.Cache base.Refresh(jsonPayload); } - /// - /// Removes the cache using the custom jsonPayload provided - /// - /// - public override void Remove(string jsonPayload) - { - var payload = DeserializeFromJsonPayload(jsonPayload); - ClearContentTypeCache(payload); - base.Remove(jsonPayload); - } - /// /// This clears out all cache associated with a content type /// diff --git a/src/Umbraco.Web/Cache/DictionaryCacheRefresher.cs b/src/Umbraco.Web/Cache/DictionaryCacheRefresher.cs new file mode 100644 index 0000000000..d9ca422f86 --- /dev/null +++ b/src/Umbraco.Web/Cache/DictionaryCacheRefresher.cs @@ -0,0 +1,42 @@ +using System; +using Umbraco.Core.Cache; + +namespace Umbraco.Web.Cache +{ + /// + /// A cache refresher to ensure the dictionary cache is refreshed when dictionary change + /// + public sealed class DictionaryCacheRefresher : CacheRefresherBase + { + protected override DictionaryCacheRefresher Instance + { + get { return this; } + } + + public override Guid UniqueIdentifier + { + get { return new Guid(DistributedCache.DictionaryCacheRefresherId); } + } + + public override string Name + { + get { return "Dictionary cache refresher"; } + } + + public override void Refresh(int id) + { + global::umbraco.cms.businesslogic.Dictionary.ClearCache(); + //when a dictionary item is updated we must also clear the text cache! + global::umbraco.cms.businesslogic.language.Item.ClearCache(); + base.Refresh(id); + } + + public override void Remove(int id) + { + global::umbraco.cms.businesslogic.Dictionary.ClearCache(); + //when a dictionary item is removed we must also clear the text cache! + global::umbraco.cms.businesslogic.language.Item.ClearCache(); + base.Remove(id); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/DistributedCache.cs b/src/Umbraco.Web/Cache/DistributedCache.cs index 29959e75e2..15d54ef151 100644 --- a/src/Umbraco.Web/Cache/DistributedCache.cs +++ b/src/Umbraco.Web/Cache/DistributedCache.cs @@ -47,6 +47,7 @@ namespace Umbraco.Web.Cache public const string StylesheetCacheRefresherId = "E0633648-0DEB-44AE-9A48-75C3A55CB670"; public const string StylesheetPropertyCacheRefresherId = "2BC7A3A4-6EB1-4FBC-BAA3-C9E7B6D36D38"; public const string DataTypeCacheRefresherId = "35B16C25-A17E-45D7-BC8F-EDAB1DCC28D2"; + public const string DictionaryCacheRefresherId = "D1D7E227-F817-4816-BFE9-6C39B6152884"; #endregion @@ -206,21 +207,7 @@ namespace Umbraco.Web.Cache GetRefresherById(factoryGuid), getNumericId, instances); - } - - /// - /// Sends a request to all registered load-balanced servers to remove data based on the custom json payload - /// using the specified ICacheRefresher with the guid factoryGuid. - /// - /// - /// - public void RemoveByJson(Guid factoryGuid, string jsonPayload) - { - ServerMessengerResolver.Current.Messenger.PerformRemove( - ServerRegistrarResolver.Current.Registrar.Registrations, - GetRefresherById(factoryGuid), - jsonPayload); - } + } private static ICacheRefresher GetRefresherById(Guid uniqueIdentifier) { diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index 736c173c44..1f72b2daed 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Cache /// /// Extension methods for DistrubutedCache /// - public static class DistributedCacheExtensions + internal static class DistributedCacheExtensions { #region User cache public static void RemoveUserCache(this DistributedCache dc, int userId) @@ -48,6 +48,30 @@ namespace Umbraco.Web.Cache #endregion + #region Dictionary cache + /// + /// Refreshes the cache amongst servers for a dictionary item + /// + /// + /// + public static void RefreshDictionaryCache(this DistributedCache dc, int dictionaryItemId) + { + dc.Refresh(new Guid(DistributedCache.DictionaryCacheRefresherId), dictionaryItemId); + } + + /// + /// Refreshes the cache amongst servers for a dictionary item + /// + /// + /// + public static void RemoveDictionaryCache(this DistributedCache dc, int dictionaryItemId) + { + dc.Remove(new Guid(DistributedCache.DictionaryCacheRefresherId), dictionaryItemId); + } + + #endregion + + #region Data type cache /// /// Refreshes the cache amongst servers for a template @@ -179,7 +203,7 @@ namespace Umbraco.Web.Cache /// public static void RemoveMediaCache(this DistributedCache dc, params IMedia[] media) { - dc.RemoveByJson(new Guid(DistributedCache.MediaCacheRefresherId), + dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId), MediaCacheRefresher.SerializeToJsonPayload(media)); } @@ -220,7 +244,7 @@ namespace Umbraco.Web.Cache { if (macro != null) { - dc.RemoveByJson(new Guid(DistributedCache.MacroCacheRefresherId), + dc.RefreshByJson(new Guid(DistributedCache.MacroCacheRefresherId), MacroCacheRefresher.SerializeToJsonPayload(macro)); } } @@ -234,7 +258,7 @@ namespace Umbraco.Web.Cache { if (macro != null && macro.Model != null) { - dc.RemoveByJson(new Guid(DistributedCache.MacroCacheRefresherId), + dc.RefreshByJson(new Guid(DistributedCache.MacroCacheRefresherId), MacroCacheRefresher.SerializeToJsonPayload(macro)); } } @@ -282,7 +306,7 @@ namespace Umbraco.Web.Cache if (contentType != null) { //dc.Remove(new Guid(DistributedCache.ContentTypeCacheRefresherId), x => x.Id, contentType); - dc.RemoveByJson(new Guid(DistributedCache.ContentTypeCacheRefresherId), + dc.RefreshByJson(new Guid(DistributedCache.ContentTypeCacheRefresherId), ContentTypeCacheRefresher.SerializeToJsonPayload(true, contentType)); } } @@ -297,7 +321,7 @@ namespace Umbraco.Web.Cache if (mediaType != null) { //dc.Remove(new Guid(DistributedCache.ContentTypeCacheRefresherId), x => x.Id, mediaType); - dc.RemoveByJson(new Guid(DistributedCache.ContentTypeCacheRefresherId), + dc.RefreshByJson(new Guid(DistributedCache.ContentTypeCacheRefresherId), ContentTypeCacheRefresher.SerializeToJsonPayload(true, mediaType)); } } diff --git a/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs b/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs index 59214b7258..09d9affa96 100644 --- a/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs @@ -31,8 +31,10 @@ namespace Umbraco.Web.Cache } public override void Remove(int id) - { + { ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.LanguageCacheKey); + //when a language is removed we must also clear the text cache! + global::umbraco.cms.businesslogic.language.Item.ClearCache(); base.Remove(id); } } diff --git a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs index 850f56f84d..e747149245 100644 --- a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs @@ -148,23 +148,18 @@ namespace Umbraco.Web.Cache } public override void Refresh(string jsonPayload) - { - Remove(jsonPayload); - base.Refresh(jsonPayload); - } - - public override void Remove(string jsonPayload) { var payloads = DeserializeFromJsonPayload(jsonPayload); payloads.ForEach(payload => - { - GetCacheKeysForAlias(payload.Alias).ForEach( - alias => - ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(alias)); - }); + { + GetCacheKeysForAlias(payload.Alias).ForEach( + alias => + ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(alias)); + }); - base.Remove(jsonPayload); + base.Refresh(jsonPayload); } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs index fab9e6264d..2536165057 100644 --- a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs @@ -95,12 +95,6 @@ namespace Umbraco.Web.Cache base.Refresh(jsonPayload); } - public override void Remove(string jsonPayload) - { - ClearCache(DeserializeFromJsonPayload(jsonPayload)); - base.Remove(jsonPayload); - } - public override void Refresh(int id) { ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id))); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2ec1e78969..b1a2e67dbb 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -265,6 +265,7 @@ + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs index 2be399f850..9a335599b4 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs @@ -132,30 +132,6 @@ namespace umbraco.presentation.webservices } } - /// - /// Removes objects using the passed in Json payload, it will be up to the cache refreshers to deserialize - /// - /// - /// A custom JSON payload used by the cache refresher - /// - /// - /// - /// NOTE: the cache refresher defined by the ID MUST be of type IJsonCacheRefresher or an exception will be thrown - /// - [WebMethod] - public void RemoveByJson(Guid uniqueIdentifier, string jsonPayload, string Login, string Password) - { - if (BusinessLogic.User.validateCredentials(Login, Password)) - { - var cr = CacheRefreshersResolver.Current.GetById(uniqueIdentifier) as IJsonCacheRefresher; - if (cr == null) - { - throw new InvalidOperationException("The cache refresher: " + uniqueIdentifier + " is not of type " + typeof(IJsonCacheRefresher)); - } - cr.Remove(jsonPayload); - } - } - [WebMethod] public void RemoveById(Guid uniqueIdentifier, int Id, string Login, string Password) { diff --git a/src/umbraco.cms/businesslogic/ContentType.cs b/src/umbraco.cms/businesslogic/ContentType.cs index f7d7e7ff25..a61ed770e9 100644 --- a/src/umbraco.cms/businesslogic/ContentType.cs +++ b/src/umbraco.cms/businesslogic/ContentType.cs @@ -245,9 +245,7 @@ namespace umbraco.cms.businesslogic private List m_AllowedChildContentTypeIDs = null; private List m_VirtualTabs = null; - - private static readonly object propertyTypesCacheSyncLock = new object(); - + protected internal IContentTypeComposition ContentTypeItem; #endregion diff --git a/src/umbraco.cms/businesslogic/Dictionary.cs b/src/umbraco.cms/businesslogic/Dictionary.cs index 16db467e61..7a655f8373 100644 --- a/src/umbraco.cms/businesslogic/Dictionary.cs +++ b/src/umbraco.cms/businesslogic/Dictionary.cs @@ -59,6 +59,16 @@ namespace umbraco.cms.businesslogic } } + /// + /// Used by the cache refreshers to clear the cache on distributed servers + /// + internal static void ClearCache() + { + DictionaryItems.Clear(); + //ensure the flag is reset so that EnsureCache will re-cache everything + _cacheIsEnsured = false; + } + /// /// Retrieve a list of toplevel DictionaryItems /// @@ -159,7 +169,8 @@ namespace umbraco.cms.businesslogic /// public bool IsTopMostItem() { - return DictionaryItems.Values.Cast() + EnsureCache(); + return DictionaryItems.Values .Where(x => x.id == id) .Select(x => x.ParentId) .SingleOrDefault() == TopLevelParent; @@ -172,9 +183,10 @@ namespace umbraco.cms.businesslogic { get { + EnsureCache(); if (_parent == null) { - var p = DictionaryItems.Values.Cast().SingleOrDefault(x => x.UniqueId == this.ParentId); + var p = DictionaryItems.Values.SingleOrDefault(x => x.UniqueId == this.ParentId); if (p == null) { @@ -203,7 +215,8 @@ namespace umbraco.cms.businesslogic { get { - return DictionaryItems.Values.Cast() + EnsureCache(); + return DictionaryItems.Values .Where(x => x.ParentId == this.UniqueId).OrderBy(item => item.key) .ToArray(); } @@ -219,7 +232,8 @@ namespace umbraco.cms.businesslogic { get { - return (SqlHelper.ExecuteScalar("select count([key]) as tmp from cmsDictionary where parent=@uniqueId", SqlHelper.CreateParameter("@uniqueId", UniqueId)) > 0); + EnsureCache(); + return DictionaryItems.Values.Any(x => x.ParentId == UniqueId); } } @@ -237,11 +251,7 @@ namespace umbraco.cms.businesslogic { SqlHelper.ExecuteNonQuery("Update cmsDictionary set [key] = @key WHERE pk = @Id", SqlHelper.CreateParameter("@key", value), SqlHelper.CreateParameter("@Id", id)); - - //remove the cached item since the key is different - DictionaryItem val; - DictionaryItems.TryRemove(key, out val); - + using (IRecordsReader dr = SqlHelper.ExecuteReader("Select pk, id, [key], parent from cmsDictionary where id=@id", SqlHelper.CreateParameter("@id", this.UniqueId))) @@ -253,8 +263,6 @@ namespace umbraco.cms.businesslogic dr.GetString("key"), dr.GetGuid("id"), dr.GetGuid("parent")); - - DictionaryItems.AddOrUpdate(item.key, s => item, (s, dictionaryItem) => item); } else { @@ -284,13 +292,13 @@ namespace umbraco.cms.businesslogic public void setValue(int languageId, string value) { - // Calling Save method triggers the Saving event - Save(); - if (Item.hasText(UniqueId, languageId)) Item.setText(languageId, UniqueId, value); else Item.addText(languageId, UniqueId, value); + + // Calling Save method triggers the Saving event + Save(); } public string Value() @@ -309,13 +317,13 @@ namespace umbraco.cms.businesslogic /// public void setValue(string value) { - // Calling Save method triggers the Saving event - Save(); - if (Item.hasText(UniqueId, 0)) Item.setText(0, UniqueId, value); else Item.addText(0, UniqueId, value); + + // Calling Save method triggers the Saving event + Save(); } public static int addKey(string key, string defaultValue, string parentKey) @@ -352,12 +360,12 @@ namespace umbraco.cms.businesslogic // remove key from database SqlHelper.ExecuteNonQuery("delete from cmsDictionary where [key] = @key", SqlHelper.CreateParameter("@key", key)); - // Remove key from cache - DictionaryItem val; - DictionaryItems.TryRemove(key, out val); + OnDeleted(EventArgs.Empty); } - [Obsolete("Does not save the dictionary item, use setValue() instead.")] + /// + /// ensures events fire after setting proeprties + /// public void Save() { OnSaving(EventArgs.Empty); @@ -467,9 +475,7 @@ namespace umbraco.cms.businesslogic dr.GetString("key"), dr.GetGuid("id"), dr.GetGuid("parent")); - - DictionaryItems.AddOrUpdate(item.key, s => item, (s, dictionaryItem) => item); - + item.setValue(defaultValue); item.OnNew(EventArgs.Empty); @@ -514,6 +520,13 @@ namespace umbraco.cms.businesslogic if (Deleting != null) Deleting(this, e); } + + public static event DeleteEventHandler Deleted; + protected virtual void OnDeleted(EventArgs e) + { + if (Deleted != null) + Deleted(this, e); + } #endregion } } diff --git a/src/umbraco.cms/businesslogic/language/Text.cs b/src/umbraco.cms/businesslogic/language/Item.cs similarity index 73% rename from src/umbraco.cms/businesslogic/language/Text.cs rename to src/umbraco.cms/businesslogic/language/Item.cs index 3c87c4a9f3..30ce7089f7 100644 --- a/src/umbraco.cms/businesslogic/language/Text.cs +++ b/src/umbraco.cms/businesslogic/language/Item.cs @@ -10,14 +10,13 @@ using System.Collections.Generic; namespace umbraco.cms.businesslogic.language { /// - /// Item class contains method for accessing language translated text, its a generic component which - /// can be used for storing language translated content, all items are associated to an unique identifier (Guid) - /// - /// The data is cached and are usable in the public website. - /// - /// Primarily used by the built-in dictionary - /// + /// THIS CLASS IS NOT INTENDED TO BE USED DIRECTLY IN YOUR CODE, USE THE umbraco.cms.businesslogic.Dictionary class instead /// + /// + /// This class is used by the DictionaryItem, all caching is handled in the DictionaryItem.Save() method which will ensure that + /// cache is invalidated if anything is changed. + /// + [Obsolete("THIS CLASS IS NOT INTENDED TO BE USED DIRECTLY IN YOUR CODE, USE THE umbraco.cms.businesslogic.Dictionary class instead")] public class Item { private static readonly ConcurrentDictionary> Items = new ConcurrentDictionary>(); @@ -36,7 +35,7 @@ namespace umbraco.cms.businesslogic.language /// /// Populates the global hash table with the data from the database. /// - private static void EnsureData() + private static void EnsureCache() { if (!_isInitialize) { @@ -54,7 +53,16 @@ namespace umbraco.cms.businesslogic.language var uniqueId = dr.GetGuid("UniqueId"); var text = dr.GetString("value"); - UpdateCache(languageId, uniqueId, text); + Items.AddOrUpdate(uniqueId, guid => + { + var languagevalues = new Dictionary { { languageId, text } }; + return languagevalues; + }, (guid, dictionary) => + { + // add/update the text for the id + dictionary[languageId] = text; + return dictionary; + }); } } _isInitialize = true; @@ -64,20 +72,16 @@ namespace umbraco.cms.businesslogic.language } } - private static void UpdateCache(int languageId, Guid key, string text) + /// + /// Clears the cache, this is used for cache refreshers to ensure that the cache is up to date across all servers + /// + internal static void ClearCache() { - Items.AddOrUpdate(key, guid => - { - var languagevalues = new Dictionary {{languageId, text}}; - return languagevalues; - }, (guid, dictionary) => - { - // add/update the text for the id - dictionary[languageId] = text; - return dictionary; - }); + Items.Clear(); + //reset the flag so that we re-lookup the cache + _isInitialize = false; } - + /// /// Retrieves the value of a languagetranslated item given the key /// @@ -86,7 +90,7 @@ namespace umbraco.cms.businesslogic.language /// The language translated text public static string Text(Guid key, int languageId) { - EnsureData(); + EnsureCache(); Dictionary val; if (Items.TryGetValue(key, out val)) @@ -104,7 +108,7 @@ namespace umbraco.cms.businesslogic.language /// returns True if there is a value associated to the unique identifier with the specified language public static bool hasText(Guid key, int languageId) { - EnsureData(); + EnsureCache(); Dictionary val; if (Items.TryGetValue(key, out val)) @@ -124,16 +128,12 @@ namespace umbraco.cms.businesslogic.language public static void setText(int languageId, Guid key, string value) { - EnsureData(); - if (!hasText(key, languageId)) throw new ArgumentException("Key does not exist"); SqlHelper.ExecuteNonQuery("Update cmsLanguageText set [value] = @value where LanguageId = @languageId And UniqueId = @key", SqlHelper.CreateParameter("@value", value), SqlHelper.CreateParameter("@languageId", languageId), SqlHelper.CreateParameter("@key", key)); - - UpdateCache(languageId, key, value); } /// @@ -145,16 +145,12 @@ namespace umbraco.cms.businesslogic.language /// public static void addText(int languageId, Guid key, string value) { - EnsureData(); - if (hasText(key, languageId)) throw new ArgumentException("Key being add'ed already exists"); SqlHelper.ExecuteNonQuery("Insert Into cmsLanguageText (languageId,UniqueId,[value]) values (@languageId, @key, @value)", SqlHelper.CreateParameter("@languageId", languageId), SqlHelper.CreateParameter("@key", key), SqlHelper.CreateParameter("@value", value)); - - UpdateCache(languageId, key, value); } /// @@ -163,15 +159,9 @@ namespace umbraco.cms.businesslogic.language /// Unique identifier public static void removeText(Guid key) { - EnsureData(); - // remove from database SqlHelper.ExecuteNonQuery("Delete from cmsLanguageText where UniqueId = @key", SqlHelper.CreateParameter("@key", key)); - - // remove from cache - Dictionary val; - Items.TryRemove(key, out val); } /// @@ -181,25 +171,10 @@ namespace umbraco.cms.businesslogic.language /// public static void RemoveByLanguage(int languageId) { - EnsureData(); - // remove from database SqlHelper.ExecuteNonQuery("Delete from cmsLanguageText where languageId = @languageId", SqlHelper.CreateParameter("@languageId", languageId)); - //we need to lock here because the inner dictionary is not concurrent, seems overkill to have a nested concurrent dictionary - lock (Locker) - { - //delete all of the items belonging to the language - foreach (var entry in Items.Values) - { - if (entry.ContainsKey(languageId)) - { - entry.Remove(languageId); - } - } - } - } } } \ No newline at end of file diff --git a/src/umbraco.cms/businesslogic/language/Language.cs b/src/umbraco.cms/businesslogic/language/Language.cs index 7d3d6236a5..55c010d655 100644 --- a/src/umbraco.cms/businesslogic/language/Language.cs +++ b/src/umbraco.cms/businesslogic/language/Language.cs @@ -82,29 +82,31 @@ namespace umbraco.cms.businesslogic.language /// Creates a new language given the culture code - ie. da-dk (denmark) /// /// Culturecode of the language - [MethodImpl(MethodImplOptions.Synchronized)] public static void MakeNew(string cultureCode) { - var culture = GetCulture(cultureCode); - if (culture != null) + lock (Locker) { - //insert it - SqlHelper.ExecuteNonQuery( - "insert into umbracoLanguage (languageISOCode) values (@CultureCode)", - SqlHelper.CreateParameter("@CultureCode", cultureCode)); - - //get it's id - var newId = SqlHelper.ExecuteScalar("SELECT MAX(id) FROM umbracoLanguage WHERE languageISOCode=@cultureCode", SqlHelper.CreateParameter("@cultureCode", cultureCode)); - - //load it and raise events - using (var dr = SqlHelper.ExecuteReader(string.Format("{0} where id = {1}", m_SQLOptimizedGetAll, newId))) + var culture = GetCulture(cultureCode); + if (culture != null) { - while (dr.Read()) + //insert it + SqlHelper.ExecuteNonQuery( + "insert into umbracoLanguage (languageISOCode) values (@CultureCode)", + SqlHelper.CreateParameter("@CultureCode", cultureCode)); + + //get it's id + var newId = SqlHelper.ExecuteScalar("SELECT MAX(id) FROM umbracoLanguage WHERE languageISOCode=@cultureCode", SqlHelper.CreateParameter("@cultureCode", cultureCode)); + + //load it and raise events + using (var dr = SqlHelper.ExecuteReader(string.Format("{0} where id = {1}", m_SQLOptimizedGetAll, newId))) { - - var ct = new Language(); - ct.PopulateFromReader(dr); - ct.OnNew(new NewEventArgs()); + while (dr.Read()) + { + + var ct = new Language(); + ct.PopulateFromReader(dr); + ct.OnNew(new NewEventArgs()); + } } } } diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index 29e453ebac..53950bc131 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -330,7 +330,7 @@ Code - + Code