diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 7e736be161..bab1269932 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -166,9 +166,15 @@ namespace Umbraco.Core /// protected virtual void InitializeResolvers() { - + //by default we'll use the standard configuration based sync ServerRegistrarResolver.Current = new ServerRegistrarResolver( - new ConfigServerRegistrar()); //by default we'll use the standard configuration based sync + new ConfigServerRegistrar()); + + //by default (outside of the web) we'll use the default server messenger without + //supplying a username/password, this will automatically disable distributed calls + // .. we'll override this in the WebBootManager + ServerMessengerResolver.Current = new ServerMessengerResolver( + new DefaultServerMessenger()); RepositoryResolver.Current = new RepositoryResolver( new RepositoryFactory()); diff --git a/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs b/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs new file mode 100644 index 0000000000..8080ef05fa --- /dev/null +++ b/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Sync +{ + /// + /// A registrar that uses the legacy xml configuration in umbracoSettings to get a list of defined server nodes + /// + internal class ConfigServerRegistrar : IServerRegistrar + { + private readonly XmlNode _xmlServers; + + public ConfigServerRegistrar() + : this(UmbracoSettings.DistributionServers) + { + + } + + internal ConfigServerRegistrar(XmlNode xmlServers) + { + _xmlServers = xmlServers; + } + + private List _addresses; + + public IEnumerable Registrations + { + get + { + if (_addresses == null) + { + _addresses = new List(); + var nodes = _xmlServers.SelectNodes("./server"); + foreach (XmlNode n in nodes) + { + _addresses.Add(new ConfigServerRegistration(n)); + } + } + return _addresses; + } + } + } +} diff --git a/src/Umbraco.Core/Sync/ConfigServerRegistration.cs b/src/Umbraco.Core/Sync/ConfigServerRegistration.cs new file mode 100644 index 0000000000..c8865bd4a2 --- /dev/null +++ b/src/Umbraco.Core/Sync/ConfigServerRegistration.cs @@ -0,0 +1,29 @@ +using System.Xml; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Sync +{ + /// + /// A server registration based on the legacy umbraco xml configuration in umbracoSettings + /// + internal class ConfigServerRegistration : IServerRegistration + { + + public ConfigServerRegistration(XmlNode n) + { + var webServicesUrl = IOHelper.ResolveUrl(SystemDirectories.WebServices); + + var protocol = GlobalSettings.UseSSL ? "https" : "http"; + if (n.Attributes.GetNamedItem("forceProtocol") != null && !string.IsNullOrEmpty(n.Attributes.GetNamedItem("forceProtocol").Value)) + protocol = n.Attributes.GetNamedItem("forceProtocol").Value; + var domain = XmlHelper.GetNodeValue(n); + if (n.Attributes.GetNamedItem("forcePortnumber") != null && !string.IsNullOrEmpty(n.Attributes.GetNamedItem("forcePortnumber").Value)) + domain += string.Format(":{0}", n.Attributes.GetNamedItem("forcePortnumber").Value); + ServerAddress = string.Format("{0}://{1}{2}/cacheRefresher.asmx", protocol, domain, webServicesUrl); + } + + public string ServerAddress { get; private set; } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/DefaultServerMessenger.cs b/src/Umbraco.Core/Sync/DefaultServerMessenger.cs new file mode 100644 index 0000000000..cc6a428ee6 --- /dev/null +++ b/src/Umbraco.Core/Sync/DefaultServerMessenger.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Threading; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using umbraco.interfaces; + +namespace Umbraco.Core.Sync +{ + /// + /// The default server messenger that uses web services to keep servers in sync + /// + internal class DefaultServerMessenger : IServerMessenger + { + private readonly string _login; + private readonly string _password; + private readonly bool _useDistributedCalls; + + /// + /// Without a username/password all distribuion will be disabled + /// + internal DefaultServerMessenger() + { + _useDistributedCalls = false; + } + + /// + /// Distribution will be enabled based on the umbraco config setting. + /// + /// + /// + internal DefaultServerMessenger(string login, string password) + { + _useDistributedCalls = UmbracoSettings.UseDistributedCalls; + _login = login; + _password = password; + } + + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher,Func getNumericId, params T[] instances) + { + if (servers == null) throw new ArgumentNullException("servers"); + if (refresher == null) throw new ArgumentNullException("refresher"); + throw new NotImplementedException(); + } + + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, Func getGuidId, params T[] instances) + { + if (servers == null) throw new ArgumentNullException("servers"); + if (refresher == null) throw new ArgumentNullException("refresher"); + throw new NotImplementedException(); + } + + public void PerformRemove(IEnumerable servers, ICacheRefresher refresher, Func getNumericId, params T[] instances) + { + if (servers == null) throw new ArgumentNullException("servers"); + if (refresher == null) throw new ArgumentNullException("refresher"); + throw new NotImplementedException(); + } + + public void PerformRemove(IEnumerable servers, ICacheRefresher refresher, params int[] numericIds) + { + if (servers == null) throw new ArgumentNullException("servers"); + if (refresher == null) throw new ArgumentNullException("refresher"); + + numericIds.ForEach(x => InvokeDispatchMethod(servers, refresher, MessageType.RemoveById, x)); + } + + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, params int[] numericIds) + { + if (servers == null) throw new ArgumentNullException("servers"); + if (refresher == null) throw new ArgumentNullException("refresher"); + + numericIds.ForEach(x => InvokeDispatchMethod(servers, refresher, MessageType.RefreshById, x)); + } + + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, params Guid[] guidIds) + { + if (servers == null) throw new ArgumentNullException("servers"); + if (refresher == null) throw new ArgumentNullException("refresher"); + + guidIds.ForEach(x => InvokeDispatchMethod(servers, refresher, MessageType.RefreshById, x)); + + } + + public void PerformRefreshAll(IEnumerable servers, ICacheRefresher refresher) + { + InvokeDispatchMethod(servers, refresher, MessageType.RefreshAll, null); + } + + private void InvokeMethodOnRefresherInstance(ICacheRefresher refresher, MessageType dispatchType, object id) + { + if (refresher == null) throw new ArgumentNullException("refresher"); + + //if we are not, then just invoke the call on the cache refresher + switch (dispatchType) + { + case MessageType.RefreshAll: + refresher.RefreshAll(); + break; + case MessageType.RefreshById: + if (id is int) + { + refresher.Refresh((int)id); + } + else if (id is Guid) + { + refresher.Refresh((Guid) id); + } + else + { + throw new InvalidOperationException("The id must be either an int or a Guid"); + } + + break; + case MessageType.RemoveById: + refresher.Remove((int)id); + break; + } + } + + private void InvokeDispatchMethod( + IEnumerable servers, + ICacheRefresher refresher, + MessageType dispatchType, + object id) + { + if (servers == null) throw new ArgumentNullException("servers"); + if (refresher == null) throw new ArgumentNullException("refresher"); + + //Now, check if we are using Distrubuted calls. If there are no servers in the list then we + // can definitely not distribute. + if (!_useDistributedCalls || !servers.Any()) + { + //if we are not, then just invoke the call on the cache refresher + InvokeMethodOnRefresherInstance(refresher, dispatchType, id); + return; + } + + //We are using distributed calls, so lets make them... + try + { + using (var cacheRefresher = new ServerSyncWebServiceClient()) + { + var asyncResultsList = new List(); + + LogStartDispatch(); + + var nodes = servers; + // Go through each configured node submitting a request asynchronously + foreach (var n in nodes) + { + //set the server address + cacheRefresher.Url = n.ServerAddress; + + // Add the returned WaitHandle to the list for later checking + switch (dispatchType) + { + case MessageType.RefreshAll: + asyncResultsList.Add( + cacheRefresher.BeginRefreshAll( + refresher.UniqueIdentifier, _login, _password, null, null)); + break; + case MessageType.RefreshById: + IAsyncResult result; + if (id is int) + { + result = cacheRefresher.BeginRefreshById(refresher.UniqueIdentifier, (int) id, _login, _password, null, null); + } + else + { + result = cacheRefresher.BeginRefreshByGuid(refresher.UniqueIdentifier, (Guid)id, _login, _password, null, null); + } + asyncResultsList.Add(result); + break; + case MessageType.RemoveById: + asyncResultsList.Add( + cacheRefresher.BeginRemoveById( + refresher.UniqueIdentifier, (int)id, _login, _password, null, null)); + break; + } + } + + List waitHandlesList; + var asyncResults = GetAsyncResults(asyncResultsList, out waitHandlesList); + + var errorCount = 0; + + // Once for each WaitHandle that we have, wait for a response and log it + // We're previously submitted all these requests effectively in parallel and will now retrieve responses on a FIFO basis + foreach (var t in asyncResults) + { + var handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15)); + + try + { + // Find out if the call succeeded + switch (dispatchType) + { + case MessageType.RefreshAll: + cacheRefresher.EndRefreshAll(t); + break; + case MessageType.RefreshById: + if (id is int) + { + cacheRefresher.EndRefreshById(t); + } + else + { + cacheRefresher.EndRefreshByGuid(t); + } + break; + case MessageType.RemoveById: + cacheRefresher.EndRemoveById(t); + break; + } + } + catch (WebException ex) + { + LogDispatchNodeError(ex); + + errorCount++; + } + catch (Exception ex) + { + LogDispatchNodeError(ex); + + errorCount++; + } + } + + LogDispatchBatchResult(errorCount); + } + } + catch (Exception ee) + { + LogDispatchBatchError(ee); + } + } + + private IEnumerable GetAsyncResults(List asyncResultsList, out List waitHandlesList) + { + var asyncResults = asyncResultsList.ToArray(); + waitHandlesList = new List(); + foreach (var asyncResult in asyncResults) + { + waitHandlesList.Add(asyncResult.AsyncWaitHandle); + } + return asyncResults; + } + + private void LogDispatchBatchError(Exception ee) + { + LogHelper.Error("Error refreshing distributed list", ee); + } + + private void LogDispatchBatchResult(int errorCount) + { + LogHelper.Debug(string.Format("Distributed server push completed with {0} nodes reporting an error", errorCount == 0 ? "no" : errorCount.ToString(CultureInfo.InvariantCulture))); + } + + private void LogDispatchNodeError(Exception ex) + { + LogHelper.Error("Error refreshing a node in the distributed list", ex); + } + + private void LogDispatchNodeError(WebException ex) + { + string url = (ex.Response != null) ? ex.Response.ResponseUri.ToString() : "invalid url (responseUri null)"; + LogHelper.Error("Error refreshing a node in the distributed list, URI attempted: " + url, ex); + } + + private void LogStartDispatch() + { + LogHelper.Info("Submitting calls to distributed servers"); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/IServerMessenger.cs b/src/Umbraco.Core/Sync/IServerMessenger.cs new file mode 100644 index 0000000000..f2112ea22d --- /dev/null +++ b/src/Umbraco.Core/Sync/IServerMessenger.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using umbraco.interfaces; + +namespace Umbraco.Core.Sync +{ + /// + /// Defines a server messenger for server sync and distrubuted cache + /// + internal interface IServerMessenger + { + /// + /// Performs a sync against all instance objects + /// + /// + /// The servers to sync against + /// + /// A delegate to return the Id for each instance to be used to sync to other servers + /// + void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, Func getNumericId, params T[] instances); + + /// + /// Performs a sync against all instance objects + /// + /// + /// The servers to sync against + /// + /// A delegate to return the Id for each instance to be used to sync to other servers + /// + void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, Func getGuidId, params T[] instances); + + /// + /// Removes the cache for the specified items + /// + /// + /// + /// + /// A delegate to return the Id for each instance to be used to sync to other servers + /// + void PerformRemove(IEnumerable servers, ICacheRefresher refresher, Func getNumericId, params T[] instances); + + /// + /// Removes the cache for the specified items + /// + /// + /// + /// + void PerformRemove(IEnumerable servers, ICacheRefresher refresher, params int[] numericIds); + + /// + /// Performs a sync against all Ids + /// + /// The servers to sync against + /// + /// + void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, params int[] numericIds); + + /// + /// Performs a sync against all Ids + /// + /// The servers to sync against + /// + /// + void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, params Guid[] guidIds); + + /// + /// Performs entire cache refresh for a specified refresher + /// + /// + /// + void PerformRefreshAll(IEnumerable servers, ICacheRefresher refresher); + } + +} \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/IServerRegistrar.cs b/src/Umbraco.Core/Sync/IServerRegistrar.cs new file mode 100644 index 0000000000..b0f6cba20f --- /dev/null +++ b/src/Umbraco.Core/Sync/IServerRegistrar.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Sync +{ + /// + /// An interface to expose a list of server registrations for server syncing + /// + internal interface IServerRegistrar + { + IEnumerable Registrations { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/IServerRegistration.cs b/src/Umbraco.Core/Sync/IServerRegistration.cs new file mode 100644 index 0000000000..a5d80180b8 --- /dev/null +++ b/src/Umbraco.Core/Sync/IServerRegistration.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Sync +{ + /// + /// An interface exposing a server address to use for server syncing + /// + internal interface IServerRegistration + { + string ServerAddress { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/MessageType.cs b/src/Umbraco.Core/Sync/MessageType.cs new file mode 100644 index 0000000000..c4c66e99e2 --- /dev/null +++ b/src/Umbraco.Core/Sync/MessageType.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Sync +{ + /// + /// The message type to be used for syncing across servers + /// + internal enum MessageType + { + RefreshAll, + RefreshById, + RemoveById + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/ServerMessengerResolver.cs b/src/Umbraco.Core/Sync/ServerMessengerResolver.cs new file mode 100644 index 0000000000..b24001c877 --- /dev/null +++ b/src/Umbraco.Core/Sync/ServerMessengerResolver.cs @@ -0,0 +1,29 @@ +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Core.Sync +{ + /// + /// A resolver to return the currently registered IServerMessenger object + /// + internal class ServerMessengerResolver : SingleObjectResolverBase + { + internal ServerMessengerResolver(IServerMessenger factory) + : base(factory) + { + } + + /// + /// Can be used at runtime to set a custom IServerMessenger at app startup + /// + /// + public void SetServerMessenger(IServerMessenger serverMessenger) + { + Value = serverMessenger; + } + + public IServerMessenger Messenger + { + get { return Value; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/ServerRegistrarResolver.cs b/src/Umbraco.Core/Sync/ServerRegistrarResolver.cs new file mode 100644 index 0000000000..3da7747c33 --- /dev/null +++ b/src/Umbraco.Core/Sync/ServerRegistrarResolver.cs @@ -0,0 +1,31 @@ +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Core.Sync +{ + /// + /// The resolver to return the currently registered IServerRegistrar object + /// + internal class ServerRegistrarResolver : SingleObjectResolverBase + { + + internal ServerRegistrarResolver(IServerRegistrar factory) + : base(factory) + { + } + + /// + /// Can be used at runtime to set a custom IServerRegistrar at app startup + /// + /// + public void SetServerRegistrar(IServerRegistrar serverRegistrar) + { + Value = serverRegistrar; + } + + public IServerRegistrar Registrar + { + get { return Value; } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/ServerSync.cs b/src/Umbraco.Core/Sync/ServerSync.cs deleted file mode 100644 index 49337c17cf..0000000000 --- a/src/Umbraco.Core/Sync/ServerSync.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Xml; -using Umbraco.Core.Configuration; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Core.Sync -{ - - internal class ConfigServerRegistrar : IServerRegistrar - { - - private List _addresses; - - public IEnumerable Registrations - { - get - { - if (_addresses == null) - { - _addresses = new List(); - var nodes = UmbracoSettings.DistributionServers.SelectNodes("./server"); - foreach (XmlNode n in nodes) - { - _addresses.Add(new ConfigServerRegistration(n)); - } - } - return _addresses; - } - } - } - - internal interface IServerRegistration - { - string ServerAddress { get; } - } - - internal class ConfigServerRegistration : IServerRegistration - { - - public ConfigServerRegistration(XmlNode n) - { - var protocol = GlobalSettings.UseSSL ? "https" : "http"; - if (n.Attributes.GetNamedItem("forceProtocol") != null && !string.IsNullOrEmpty(n.Attributes.GetNamedItem("forceProtocol").Value)) - protocol = n.Attributes.GetNamedItem("forceProtocol").Value; - var domain = XmlHelper.GetNodeValue(n); - if (n.Attributes.GetNamedItem("forcePortnumber") != null && !string.IsNullOrEmpty(n.Attributes.GetNamedItem("forcePortnumber").Value)) - domain += string.Format(":{0}", n.Attributes.GetNamedItem("forcePortnumber").Value); - ServerAddress = string.Format("{0}://{1}{2}", protocol, domain); - } - - public string ServerAddress { get; private set; } - - } - - internal interface IServerRegistrar - { - IEnumerable Registrations { get; } - } - - internal class ServerRegistrarResolver : SingleObjectResolverBase - { - - internal ServerRegistrarResolver(IServerRegistrar factory) - : base(factory) - { - } - - public IServerRegistrar Registrar - { - get { return Value; } - } - - } -} diff --git a/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs b/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs new file mode 100644 index 0000000000..4df81e8968 --- /dev/null +++ b/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs @@ -0,0 +1,154 @@ +using System.Web.Services; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Sync +{ + + /// + /// The client Soap service for making distrubuted cache calls between servers + /// + [WebServiceBinding(Name = "CacheRefresherSoap", Namespace = "http://umbraco.org/webservices/")] + internal class ServerSyncWebServiceClient : System.Web.Services.Protocols.SoapHttpClientProtocol + { + + /// + public ServerSyncWebServiceClient() + { + // only set the url if the httpcontext is present, else it's set by the cache dispatcher methods (when using distributed calls) + if (System.Web.HttpContext.Current != null) + this.Url = "http://" + System.Web.HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + IOHelper.ResolveUrl(SystemDirectories.WebServices) + "/cacheRefresher.asmx"; + + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshAll", 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 RefreshAll(System.Guid uniqueIdentifier, string Login, string Password) + { + this.Invoke("RefreshAll", new object[] { + uniqueIdentifier, + Login, + Password}); + } + + /// + public System.IAsyncResult BeginRefreshAll(System.Guid uniqueIdentifier, string Login, string Password, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("RefreshAll", new object[] { + uniqueIdentifier, + Login, + Password}, callback, asyncState); + } + + /// + public void EndRefreshAll(System.IAsyncResult asyncResult) + { + this.EndInvoke(asyncResult); + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByGuid", 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 RefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password) + { + this.Invoke("RefreshByGuid", new object[] { + uniqueIdentifier, + Id, + Login, + Password}); + } + + /// + public System.IAsyncResult BeginRefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("RefreshByGuid", new object[] { + uniqueIdentifier, + Id, + Login, + Password}, callback, asyncState); + } + + /// + public void EndRefreshByGuid(System.IAsyncResult asyncResult) + { + this.EndInvoke(asyncResult); + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshById", 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 RefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password) + { + this.Invoke("RefreshById", new object[] { + uniqueIdentifier, + Id, + Login, + Password}); + } + + /// + public System.IAsyncResult BeginRefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("RefreshById", new object[] { + uniqueIdentifier, + Id, + Login, + Password}, callback, asyncState); + } + + /// + public void EndRefreshById(System.IAsyncResult asyncResult) + { + this.EndInvoke(asyncResult); + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RemoveById", 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 RemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password) + { + this.Invoke("RemoveById", new object[] { + uniqueIdentifier, + Id, + Login, + Password}); + } + + /// + public System.IAsyncResult BeginRemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("RemoveById", new object[] { + uniqueIdentifier, + Id, + Login, + Password}, callback, asyncState); + } + + /// + public void EndRemoveById(System.IAsyncResult asyncResult) + { + this.EndInvoke(asyncResult); + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/GetRefreshers", 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 System.Xml.XmlNode GetRefreshers(string Login, string Password) + { + object[] results = this.Invoke("GetRefreshers", new object[] { + Login, + Password}); + return ((System.Xml.XmlNode)(results[0])); + } + + /// + public System.IAsyncResult BeginGetRefreshers(string Login, string Password, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("GetRefreshers", new object[] { + Login, + Password}, callback, asyncState); + } + + /// + public System.Xml.XmlNode EndGetRefreshers(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((System.Xml.XmlNode)(results[0])); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 780239f295..7420e3018a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -73,6 +73,7 @@ True ..\packages\Microsoft.AspNet.Razor.2.0.20715.0\lib\net40\System.Web.Razor.dll + True ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.dll @@ -667,7 +668,18 @@ - + + + Component + + + + + + + + + diff --git a/src/Umbraco.Tests/Sync/ConfigServerRegistrarTests.cs b/src/Umbraco.Tests/Sync/ConfigServerRegistrarTests.cs new file mode 100644 index 0000000000..c537c19072 --- /dev/null +++ b/src/Umbraco.Tests/Sync/ConfigServerRegistrarTests.cs @@ -0,0 +1,45 @@ +using System.Linq; +using System.Text; +using System.Xml; +using NUnit.Framework; +using Umbraco.Core.Sync; +using Umbraco.Tests.PartialTrust; + +namespace Umbraco.Tests.Sync +{ + [TestFixture] + public class ConfigServerRegistrarTests + { + + [TestCase("127.0.0.1", "http://127.0.0.1")] + [TestCase("www.somedomain.com", "https://www.somedomain.com")] + [TestCase("another.domain.com.au", "http://another.domain.com.au:888")] + [TestCase("another.domain.com.au", "https://another.domain.com.au:999")] + public void Ensure_Correct_Format(string xml, string match) + { + var xDoc = new XmlDocument(); + xDoc.LoadXml(xml); + var xNode = xDoc.FirstChild; + var cReg = new ConfigServerRegistration(xNode); + + Assert.AreEqual(match + "/umbraco/webservices/cacheRefresher.asmx", cReg.ServerAddress); + } + + [Test] + public void Ensure_Parses_Config_Block() + { + var xDoc = new XmlDocument(); + xDoc.LoadXml(@" + 127.0.0.1 + www.somedomain.com + another.domain.com.au + another.domain.com.au +"); + var xNode = xDoc.FirstChild; + var cReg = new ConfigServerRegistrar(xNode); + + Assert.AreEqual(4, cReg.Registrations.Count()); + } + + } +} diff --git a/src/Umbraco.Tests/Sync/DistributedCacheTests.cs b/src/Umbraco.Tests/Sync/DistributedCacheTests.cs new file mode 100644 index 0000000000..b2a6cca09e --- /dev/null +++ b/src/Umbraco.Tests/Sync/DistributedCacheTests.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; +using umbraco.interfaces; + +namespace Umbraco.Tests.Sync +{ + /// + /// Ensures that calls to DistributedCache methods carry through to the IServerMessenger correctly + /// + [TestFixture] + public class DistributedCacheTests + { + [SetUp] + public void Setup() + { + ServerRegistrarResolver.Current = new ServerRegistrarResolver( + new TestServerRegistrar()); + ServerMessengerResolver.Current = new ServerMessengerResolver( + new TestServerMessenger()); + CacheRefreshersResolver.Current = new CacheRefreshersResolver(() => new[] { typeof(TestCacheRefresher) }); + Resolution.Freeze(); + } + + [TearDown] + public void Teardown() + { + ServerRegistrarResolver.Reset(); + ServerMessengerResolver.Reset(); + CacheRefreshersResolver.Reset(); + } + + [Test] + public void RefreshIntId() + { + for (var i = 0; i < 10; i++) + { + DistributedCache.Instance.Refresh(Guid.Parse("E0F452CB-DCB2-4E84-B5A5-4F01744C5C73"), i); + } + Assert.AreEqual(10, ((TestServerMessenger)ServerMessengerResolver.Current.Messenger).IntIdsRefreshed.Count); + } + + [Test] + public void RefreshGuidId() + { + for (var i = 0; i < 11; i++) + { + DistributedCache.Instance.Refresh(Guid.Parse("E0F452CB-DCB2-4E84-B5A5-4F01744C5C73"), Guid.NewGuid()); + } + Assert.AreEqual(11, ((TestServerMessenger)ServerMessengerResolver.Current.Messenger).GuidIdsRefreshed.Count); + } + + [Test] + public void RemoveIds() + { + for (var i = 0; i < 12; i++) + { + DistributedCache.Instance.Remove(Guid.Parse("E0F452CB-DCB2-4E84-B5A5-4F01744C5C73"), i); + } + Assert.AreEqual(12, ((TestServerMessenger)ServerMessengerResolver.Current.Messenger).IntIdsRemoved.Count); + } + + [Test] + public void FullRefreshes() + { + for (var i = 0; i < 13; i++) + { + DistributedCache.Instance.RefreshAll(Guid.Parse("E0F452CB-DCB2-4E84-B5A5-4F01744C5C73")); + } + Assert.AreEqual(13, ((TestServerMessenger)ServerMessengerResolver.Current.Messenger).CountOfFullRefreshes); + } + + #region internal test classes + + internal class TestCacheRefresher : ICacheRefresher + { + public Guid UniqueIdentifier + { + get { return Guid.Parse("E0F452CB-DCB2-4E84-B5A5-4F01744C5C73"); } + } + public string Name + { + get { return "Test"; } + } + public void RefreshAll() + { + + } + + public void Refresh(int id) + { + + } + + public void Remove(int id) + { + + } + + public void Refresh(Guid id) + { + + } + } + + internal class TestServerMessenger : IServerMessenger + { + //used for tests + public List IntIdsRefreshed = new List(); + public List GuidIdsRefreshed = new List(); + public List IntIdsRemoved = new List(); + public int CountOfFullRefreshes = 0; + + + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, Func getNumericId, params T[] instances) + { + throw new NotImplementedException(); + } + + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, Func getGuidId, params T[] instances) + { + throw new NotImplementedException(); + } + + public void PerformRemove(IEnumerable servers, ICacheRefresher refresher, Func getNumericId, params T[] instances) + { + throw new NotImplementedException(); + } + + public void PerformRemove(IEnumerable servers, ICacheRefresher refresher, params int[] numericIds) + { + IntIdsRemoved.AddRange(numericIds); + } + + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, params int[] numericIds) + { + IntIdsRefreshed.AddRange(numericIds); + } + + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, params Guid[] guidIds) + { + GuidIdsRefreshed.AddRange(guidIds); + } + + public void PerformRefreshAll(IEnumerable servers, ICacheRefresher refresher) + { + CountOfFullRefreshes++; + } + } + + internal class TestServerRegistrar : IServerRegistrar + { + public IEnumerable Registrations + { + get + { + return new List() + { + new TestServerRegistration("localhost") + }; + } + } + } + + public class TestServerRegistration : IServerRegistration + { + public TestServerRegistration(string address) + { + ServerAddress = address; + } + public string ServerAddress { get; private set; } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 96438c48b2..8ca2e35506 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -261,6 +261,8 @@ + + diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index 1fafe2f61b..e1e02de465 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -150,14 +150,16 @@ - - - - 0 - - - - + + + + 0 + + + + umblb1.dev + localhost + diff --git a/src/Umbraco.Web/Cache/CacheRefresherClient.cs b/src/Umbraco.Web/Cache/CacheRefresherClient.cs index a382b3fd26..2943c8579c 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherClient.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherClient.cs @@ -1,155 +1,155 @@ -using Umbraco.Core.IO; +//using Umbraco.Core.IO; -namespace Umbraco.Web.Cache -{ +//namespace Umbraco.Web.Cache +//{ - /// - /// The client Soap service for making distrubuted cache calls between servers - /// - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Web.Services.WebServiceBindingAttribute(Name = "CacheRefresherSoap", Namespace = "http://umbraco.org/webservices/")] - internal class CacheRefresherClient : System.Web.Services.Protocols.SoapHttpClientProtocol - { +// /// +// /// The client Soap service for making distrubuted cache calls between servers +// /// +// [System.Diagnostics.DebuggerStepThroughAttribute()] +// [System.ComponentModel.DesignerCategoryAttribute("code")] +// [System.Web.Services.WebServiceBindingAttribute(Name = "CacheRefresherSoap", Namespace = "http://umbraco.org/webservices/")] +// internal class CacheRefresherClient : System.Web.Services.Protocols.SoapHttpClientProtocol +// { - /// - public CacheRefresherClient() - { - // only set the url if the httpcontext is present, else it's set by the cache dispatcher methods (when using distributed calls) - if (System.Web.HttpContext.Current != null) - this.Url = "http://" + System.Web.HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + IOHelper.ResolveUrl(SystemDirectories.WebServices) + "/cacheRefresher.asmx"; +// /// +// public CacheRefresherClient() +// { +// // only set the url if the httpcontext is present, else it's set by the cache dispatcher methods (when using distributed calls) +// if (System.Web.HttpContext.Current != null) +// this.Url = "http://" + System.Web.HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + IOHelper.ResolveUrl(SystemDirectories.WebServices) + "/cacheRefresher.asmx"; - } +// } - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshAll", 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 RefreshAll(System.Guid uniqueIdentifier, string Login, string Password) - { - this.Invoke("RefreshAll", new object[] { - uniqueIdentifier, - Login, - Password}); - } +// /// +// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshAll", 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 RefreshAll(System.Guid uniqueIdentifier, string Login, string Password) +// { +// this.Invoke("RefreshAll", new object[] { +// uniqueIdentifier, +// Login, +// Password}); +// } - /// - public System.IAsyncResult BeginRefreshAll(System.Guid uniqueIdentifier, string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("RefreshAll", new object[] { - uniqueIdentifier, - Login, - Password}, callback, asyncState); - } +// /// +// public System.IAsyncResult BeginRefreshAll(System.Guid uniqueIdentifier, string Login, string Password, System.AsyncCallback callback, object asyncState) +// { +// return this.BeginInvoke("RefreshAll", new object[] { +// uniqueIdentifier, +// Login, +// Password}, callback, asyncState); +// } - /// - public void EndRefreshAll(System.IAsyncResult asyncResult) - { - this.EndInvoke(asyncResult); - } +// /// +// public void EndRefreshAll(System.IAsyncResult asyncResult) +// { +// this.EndInvoke(asyncResult); +// } - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByGuid", 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 RefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password) - { - this.Invoke("RefreshByGuid", new object[] { - uniqueIdentifier, - Id, - Login, - Password}); - } +// /// +// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByGuid", 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 RefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password) +// { +// this.Invoke("RefreshByGuid", new object[] { +// uniqueIdentifier, +// Id, +// Login, +// Password}); +// } - /// - public System.IAsyncResult BeginRefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("RefreshByGuid", new object[] { - uniqueIdentifier, - Id, - Login, - Password}, callback, asyncState); - } +// /// +// public System.IAsyncResult BeginRefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password, System.AsyncCallback callback, object asyncState) +// { +// return this.BeginInvoke("RefreshByGuid", new object[] { +// uniqueIdentifier, +// Id, +// Login, +// Password}, callback, asyncState); +// } - /// - public void EndRefreshByGuid(System.IAsyncResult asyncResult) - { - this.EndInvoke(asyncResult); - } +// /// +// public void EndRefreshByGuid(System.IAsyncResult asyncResult) +// { +// this.EndInvoke(asyncResult); +// } - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshById", 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 RefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password) - { - this.Invoke("RefreshById", new object[] { - uniqueIdentifier, - Id, - Login, - Password}); - } +// /// +// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshById", 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 RefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password) +// { +// this.Invoke("RefreshById", new object[] { +// uniqueIdentifier, +// Id, +// Login, +// Password}); +// } - /// - public System.IAsyncResult BeginRefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("RefreshById", new object[] { - uniqueIdentifier, - Id, - Login, - Password}, callback, asyncState); - } +// /// +// public System.IAsyncResult BeginRefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password, System.AsyncCallback callback, object asyncState) +// { +// return this.BeginInvoke("RefreshById", new object[] { +// uniqueIdentifier, +// Id, +// Login, +// Password}, callback, asyncState); +// } - /// - public void EndRefreshById(System.IAsyncResult asyncResult) - { - this.EndInvoke(asyncResult); - } +// /// +// public void EndRefreshById(System.IAsyncResult asyncResult) +// { +// this.EndInvoke(asyncResult); +// } - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RemoveById", 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 RemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password) - { - this.Invoke("RemoveById", new object[] { - uniqueIdentifier, - Id, - Login, - Password}); - } +// /// +// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RemoveById", 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 RemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password) +// { +// this.Invoke("RemoveById", new object[] { +// uniqueIdentifier, +// Id, +// Login, +// Password}); +// } - /// - public System.IAsyncResult BeginRemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("RemoveById", new object[] { - uniqueIdentifier, - Id, - Login, - Password}, callback, asyncState); - } +// /// +// public System.IAsyncResult BeginRemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password, System.AsyncCallback callback, object asyncState) +// { +// return this.BeginInvoke("RemoveById", new object[] { +// uniqueIdentifier, +// Id, +// Login, +// Password}, callback, asyncState); +// } - /// - public void EndRemoveById(System.IAsyncResult asyncResult) - { - this.EndInvoke(asyncResult); - } +// /// +// public void EndRemoveById(System.IAsyncResult asyncResult) +// { +// this.EndInvoke(asyncResult); +// } - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/GetRefreshers", 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 System.Xml.XmlNode GetRefreshers(string Login, string Password) - { - object[] results = this.Invoke("GetRefreshers", new object[] { - Login, - Password}); - return ((System.Xml.XmlNode)(results[0])); - } +// /// +// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/GetRefreshers", 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 System.Xml.XmlNode GetRefreshers(string Login, string Password) +// { +// object[] results = this.Invoke("GetRefreshers", new object[] { +// Login, +// Password}); +// return ((System.Xml.XmlNode)(results[0])); +// } - /// - public System.IAsyncResult BeginGetRefreshers(string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("GetRefreshers", new object[] { - Login, - Password}, callback, asyncState); - } +// /// +// public System.IAsyncResult BeginGetRefreshers(string Login, string Password, System.AsyncCallback callback, object asyncState) +// { +// return this.BeginInvoke("GetRefreshers", new object[] { +// Login, +// Password}, callback, asyncState); +// } - /// - public System.Xml.XmlNode EndGetRefreshers(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((System.Xml.XmlNode)(results[0])); - } - } -} \ No newline at end of file +// /// +// public System.Xml.XmlNode EndGetRefreshers(System.IAsyncResult asyncResult) +// { +// object[] results = this.EndInvoke(asyncResult); +// return ((System.Xml.XmlNode)(results[0])); +// } +// } +//} \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/DistributedCache.cs b/src/Umbraco.Web/Cache/DistributedCache.cs index 670592b325..a02f3bb637 100644 --- a/src/Umbraco.Web/Cache/DistributedCache.cs +++ b/src/Umbraco.Web/Cache/DistributedCache.cs @@ -16,17 +16,13 @@ using umbraco.interfaces; namespace Umbraco.Web.Cache { /// - /// DistrubutedCacheDispatcher is used to handle Umbraco's load balancing. + /// DistributedCache is used to invalidate cache throughout the application which also takes in to account load balancing environments automatically /// /// /// Distributing calls to all registered load balanced servers, ensuring that content are synced and cached on all servers. /// Dispatcher is exendable, so 3rd party services can easily be integrated into the workflow, using the interfaces.ICacheRefresher interface. /// /// Dispatcher can refresh/remove content, templates and macros. - /// Load balanced servers are registered in umbracoSettings.config. - /// - /// UPDATE 2010 02 - Alex Norcliffe - Refactored Dispatcher to support parallel dispatch threads, and preventing failure of whole dispatch - /// if one node fails. Still needs more work to get it to Enterprise level though but this is for 4.1 /// public class DistributedCache { @@ -41,19 +37,13 @@ namespace Umbraco.Web.Cache #endregion - private readonly string _login; - private readonly string _password; - private readonly string _webServicesUrl; private static readonly DistributedCache InstanceObject = new DistributedCache(); /// /// Constructor /// private DistributedCache() - { - _login = User.GetUser(UmbracoSettings.DistributedCallUser).LoginName; - _password = User.GetUser(UmbracoSettings.DistributedCallUser).GetPassword(); - _webServicesUrl = IOHelper.ResolveUrl(SystemDirectories.WebServices); + { } /// @@ -76,7 +66,10 @@ namespace Umbraco.Web.Cache /// The id of the node. public void Refresh(Guid factoryGuid, int id) { - InvokeDispatchMethod(DispatchType.RefreshByNumericId, factoryGuid, id, Guid.Empty); + ServerMessengerResolver.Current.Messenger.PerformRefresh( + ServerRegistrarResolver.Current.Registrar.Registrations, + GetRefresherById(factoryGuid), + id); } /// @@ -87,7 +80,10 @@ namespace Umbraco.Web.Cache /// The guid of the node. public void Refresh(Guid factoryGuid, Guid id) { - InvokeDispatchMethod(DispatchType.RefreshByGuid, factoryGuid, 0, id); + ServerMessengerResolver.Current.Messenger.PerformRefresh( + ServerRegistrarResolver.Current.Registrar.Registrations, + GetRefresherById(factoryGuid), + id); } /// @@ -97,7 +93,9 @@ namespace Umbraco.Web.Cache /// The unique identifier. public void RefreshAll(Guid factoryGuid) { - InvokeDispatchMethod(DispatchType.RefreshAll, factoryGuid, 0, Guid.Empty); + ServerMessengerResolver.Current.Messenger.PerformRefreshAll( + ServerRegistrarResolver.Current.Registrar.Registrations, + GetRefresherById(factoryGuid)); } /// @@ -108,192 +106,10 @@ namespace Umbraco.Web.Cache /// The id. public void Remove(Guid factoryGuid, int id) { - InvokeDispatchMethod(DispatchType.RemoveById, factoryGuid, id, Guid.Empty); - } - - /// - /// Used to invoke the method on an ICacheRefresher instance if we are not currently using distributed calls. - /// - /// - /// - /// - /// - private void InvokeMethodOnRefresherInstance(ICacheRefresher refresher, DispatchType dispatchType, int numericId, Guid guidId) - { - //if we are not, then just invoke the call on the cache refresher - switch (dispatchType) - { - case DispatchType.RefreshAll: - refresher.RefreshAll(); - break; - case DispatchType.RefreshByNumericId: - refresher.Refresh(numericId); - break; - case DispatchType.RefreshByGuid: - refresher.Refresh(guidId); - break; - case DispatchType.RemoveById: - refresher.Remove(numericId); - break; - } - } - - /// - /// Invokes the relevant dispatch method. - /// - /// Type of the dispatch. - /// The factory GUID. - /// The numeric id. - /// The GUID id. - private void InvokeDispatchMethod(DispatchType dispatchType, Guid factoryGuid, int numericId, Guid guidId) - { - //get the refresher, it must be found or else we cannot continue - var refresher = GetRefresherById(factoryGuid); - if (refresher == null) - { - var ex = new InvalidOperationException( - "Could not find an " + typeof(ICacheRefresher).Name + " with the Id " + guidId); - LogHelper.Error("Could not continue with DistributedCache call", ex); - return; - } - - //Now, check if we are using Distrubuted calls - if (!UmbracoSettings.UseDistributedCalls) - { - //if we are not, then just invoke the call on the cache refresher - InvokeMethodOnRefresherInstance(refresher, dispatchType, numericId, guidId); - return; - } - - //We are using distributed calls, so lets make them... - try - { - using (var cacheRefresher = new CacheRefresherClient()) - { - var asyncResultsList = new List(); - - LogStartDispatch(); - - //var nodes = - - // Go through each configured node submitting a request asynchronously - foreach (XmlNode n in GetDistributedNodes()) - { - SetWebServiceUrlFromNode(cacheRefresher, n); - - // Add the returned WaitHandle to the list for later checking - switch (dispatchType) - { - case DispatchType.RefreshAll: - asyncResultsList.Add(cacheRefresher.BeginRefreshAll(factoryGuid, _login, _password, null, - null)); - break; - case DispatchType.RefreshByGuid: - asyncResultsList.Add(cacheRefresher.BeginRefreshByGuid(factoryGuid, guidId, _login, - _password, null, null)); - break; - case DispatchType.RefreshByNumericId: - asyncResultsList.Add(cacheRefresher.BeginRefreshById(factoryGuid, numericId, _login, - _password, null, null)); - break; - case DispatchType.RemoveById: - asyncResultsList.Add(cacheRefresher.BeginRemoveById(factoryGuid, numericId, _login, - _password, null, null)); - break; - } - } - - - List waitHandlesList; - IAsyncResult[] asyncResults = GetAsyncResults(asyncResultsList, out waitHandlesList); - - int errorCount = 0; - - // Once for each WaitHandle that we have, wait for a response and log it - // We're previously submitted all these requests effectively in parallel and will now retrieve responses on a FIFO basis - for (int waitCalls = 0; waitCalls < asyncResults.Length; waitCalls++) - { - int handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15)); - - try - { - // Find out if the call succeeded - switch (dispatchType) - { - case DispatchType.RefreshAll: - cacheRefresher.EndRefreshAll(asyncResults[waitCalls]); - break; - case DispatchType.RefreshByGuid: - cacheRefresher.EndRefreshByGuid(asyncResults[waitCalls]); - break; - case DispatchType.RefreshByNumericId: - cacheRefresher.EndRefreshById(asyncResults[waitCalls]); - break; - case DispatchType.RemoveById: - cacheRefresher.EndRemoveById(asyncResults[waitCalls]); - break; - } - } - catch (WebException ex) - { - LogDispatchNodeError(ex); - - errorCount++; - } - catch (Exception ex) - { - LogDispatchNodeError(ex); - - errorCount++; - } - } - - LogDispatchBatchResult(errorCount); - } - } - catch (Exception ee) - { - LogDispatchBatchError(ee); - } - } - - private void LogDispatchBatchError(Exception ee) - { - LogHelper.Error("Error refreshing distributed list", ee); - } - - private void LogDispatchBatchResult(int errorCount) - { - LogHelper.Debug(string.Format("Distributed server push completed with {0} nodes reporting an error", errorCount == 0 ? "no" : errorCount.ToString(CultureInfo.InvariantCulture))); - } - - private void LogDispatchNodeError(Exception ex) - { - LogHelper.Error("Error refreshing a node in the distributed list", ex); - } - - private void LogDispatchNodeError(WebException ex) - { - string url = (ex.Response != null) ? ex.Response.ResponseUri.ToString() : "invalid url (responseUri null)"; - LogHelper.Error("Error refreshing a node in the distributed list, URI attempted: " + url, ex); - } - - /// - /// Sets the web service URL for a CacheRefresher from an XmlNode. - /// - /// The CacheRefresher. - /// The XmlNode. - private void SetWebServiceUrlFromNode(WebClientProtocol cr, XmlNode n) - { - //string protocol = GlobalSettings.UseSSL ? "https" : "http"; - //if (n.Attributes.GetNamedItem("forceProtocol") != null && !String.IsNullOrEmpty(n.Attributes.GetNamedItem("forceProtocol").Value)) - // protocol = n.Attributes.GetNamedItem("forceProtocol").Value; - //string domain = XmlHelper.GetNodeValue(n); - //if (n.Attributes.GetNamedItem("forcePortnumber") != null && !String.IsNullOrEmpty(n.Attributes.GetNamedItem("forcePortnumber").Value)) - // domain += string.Format(":{0}", n.Attributes.GetNamedItem("forcePortnumber").Value); - //cr.Url = string.Format("{0}://{1}{2}/cacheRefresher.asmx", protocol, domain, _webServicesUrl); - - //ServerRegistrarResolver.Current.Registrar.Registrations + ServerMessengerResolver.Current.Messenger.PerformRemove( + ServerRegistrarResolver.Current.Registrar.Registrations, + GetRefresherById(factoryGuid), + id); } private static ICacheRefresher GetRefresherById(Guid uniqueIdentifier) @@ -301,42 +117,5 @@ namespace Umbraco.Web.Cache return CacheRefreshersResolver.Current.GetById(uniqueIdentifier); } - private void LogStartDispatch() - { - LogHelper.Info("Submitting calls to distributed servers"); - } - - /// - /// Gets the node list of DistributionServers from config. - /// - /// - private XmlNodeList GetDistributedNodes() - { - return UmbracoSettings.DistributionServers.SelectNodes("./server"); - } - - private IAsyncResult[] GetAsyncResults(List asyncResultsList, - out List waitHandlesList) - { - IAsyncResult[] asyncResults = asyncResultsList.ToArray(); - waitHandlesList = new List(); - foreach (IAsyncResult asyncResult in asyncResults) - { - waitHandlesList.Add(asyncResult.AsyncWaitHandle); - } - return asyncResults; - } - - #region Nested type: DispatchType - - private enum DispatchType - { - RefreshAll, - RefreshByNumericId, - RefreshByGuid, - RemoveById - } - - #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 907a8efdb8..25c6d6fc16 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -7,8 +7,10 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; using Umbraco.Core.Dynamics; +using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Sync; using Umbraco.Web.Dictionary; using Umbraco.Web.Media; using Umbraco.Web.Media.ThumbnailProviders; @@ -16,6 +18,7 @@ using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.PropertyEditors; using Umbraco.Web.Routing; +using umbraco.BusinessLogic; using umbraco.businesslogic; using umbraco.cms.businesslogic; using umbraco.presentation.cache; @@ -173,6 +176,24 @@ namespace Umbraco.Web { base.InitializeResolvers(); + //we should not proceed to change this if the app/database is not configured since there will + // be no user, plus we don't need to have server messages sent if this is the case. + if (ApplicationContext.IsConfigured && ApplicationContext.DatabaseContext.IsDatabaseConfigured) + { + var user = User.GetUser(UmbracoSettings.DistributedCallUser); + try + { + //Override the ServerMessengerResolver to set a username/password for the distributed calls + ServerMessengerResolver.Current.SetServerMessenger(new DefaultServerMessenger( + user.LoginName, + user.GetPassword())); + } + catch (Exception e) + { + LogHelper.Error("An error occurred trying to set the IServerMessenger during application startup", e); + } + } + //We are going to manually remove a few cache refreshers here because we've obsoleted them and we don't want them // to be registered more than once CacheRefreshersResolver.Current.RemoveType(); diff --git a/src/UmbracoExamine/BaseUmbracoIndexer.cs b/src/UmbracoExamine/BaseUmbracoIndexer.cs index dc6c1c704f..fd9a1e8c16 100644 --- a/src/UmbracoExamine/BaseUmbracoIndexer.cs +++ b/src/UmbracoExamine/BaseUmbracoIndexer.cs @@ -95,7 +95,7 @@ namespace UmbracoExamine { //We need to check if we actually can initialize, if not then don't continue - if (!CanInitialized()) + if (!CanInitialize()) { return; } @@ -173,7 +173,8 @@ namespace UmbracoExamine /// Returns true if the Umbraco application is in a state that we can initialize the examine indexes /// /// - protected bool CanInitialized() + [SecuritySafeCritical] + protected bool CanInitialize() { //We need to check if we actually can initialize, if not then don't continue if (ApplicationContext.Current == null diff --git a/src/UmbracoExamine/DataServices/UmbracoMediaService.cs b/src/UmbracoExamine/DataServices/UmbracoMediaService.cs index a324903c41..c1bd2c71d3 100644 --- a/src/UmbracoExamine/DataServices/UmbracoMediaService.cs +++ b/src/UmbracoExamine/DataServices/UmbracoMediaService.cs @@ -19,12 +19,14 @@ namespace UmbracoExamine.DataServices { private readonly ServiceContext _services; + [SecuritySafeCritical] public UmbracoMediaService() : this(ApplicationContext.Current.Services) { } + [SecuritySafeCritical] public UmbracoMediaService(ServiceContext services) { _services = services; diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index 1fb1a19c69..7d3f22a187 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -121,7 +121,7 @@ namespace UmbracoExamine { //We need to check if we actually can initialize, if not then don't continue - if (!CanInitialized()) + if (!CanInitialize()) { return; } diff --git a/src/UmbracoExamine/UmbracoExamineSearcher.cs b/src/UmbracoExamine/UmbracoExamineSearcher.cs index b0fc1a54e9..a123bb4160 100644 --- a/src/UmbracoExamine/UmbracoExamineSearcher.cs +++ b/src/UmbracoExamine/UmbracoExamineSearcher.cs @@ -34,7 +34,7 @@ namespace UmbracoExamine public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { //We need to check if we actually can initialize, if not then don't continue - if (!CanInitialized()) + if (!CanInitialize()) { return; } @@ -70,7 +70,8 @@ namespace UmbracoExamine /// Returns true if the Umbraco application is in a state that we can initialize the examine indexes /// /// - protected bool CanInitialized() + [SecuritySafeCritical] + protected bool CanInitialize() { //We need to check if we actually can initialize, if not then don't continue if (ApplicationContext.Current == null