using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Logging; namespace Umbraco.Core.Sync { /// /// Provides a base class for all implementations. /// public abstract class ServerMessengerBase : IServerMessenger { protected bool DistributedEnabled { get; set; } protected ServerMessengerBase(bool distributedEnabled) { DistributedEnabled = distributedEnabled; } /// /// Determines whether to make distributed calls when messaging a cache refresher. /// /// The registered servers. /// The cache refresher. /// The message type. /// true if distributed calls are required; otherwise, false, all we have is the local server. protected virtual bool RequiresDistributed(ICacheRefresher refresher, MessageType messageType) { return DistributedEnabled; } // ensures that all items in the enumerable are of the same type, either int or Guid. protected static bool GetArrayType(IEnumerable ids, out Type arrayType) { arrayType = null; if (ids == null) return true; foreach (var id in ids) { // only int and Guid are supported if ((id is int) == false && ((id is Guid) == false)) return false; // initialize with first item if (arrayType == null) arrayType = id.GetType(); // check remaining items if (arrayType != id.GetType()) return false; } return true; } #region IServerMessenger public void PerformRefresh(ICacheRefresher refresher, TPayload[] payload) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); if (payload == null) throw new ArgumentNullException(nameof(payload)); Deliver(refresher, payload); } public void PerformRefresh(ICacheRefresher refresher, string jsonPayload) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); if (jsonPayload == null) throw new ArgumentNullException(nameof(jsonPayload)); Deliver(refresher, MessageType.RefreshByJson, json: jsonPayload); } public void PerformRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); if (getNumericId == null) throw new ArgumentNullException(nameof(getNumericId)); if (instances == null || instances.Length == 0) return; Func getId = x => getNumericId(x); Deliver(refresher, MessageType.RefreshByInstance, getId, instances); } public void PerformRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); if (getGuidId == null) throw new ArgumentNullException(nameof(getGuidId)); if (instances == null || instances.Length == 0) return; Func getId = x => getGuidId(x); Deliver(refresher, MessageType.RefreshByInstance, getId, instances); } public void PerformRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); if (getNumericId == null) throw new ArgumentNullException(nameof(getNumericId)); if (instances == null || instances.Length == 0) return; Func getId = x => getNumericId(x); Deliver(refresher, MessageType.RemoveByInstance, getId, instances); } public void PerformRemove(ICacheRefresher refresher, params int[] numericIds) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); if (numericIds == null || numericIds.Length == 0) return; Deliver(refresher, MessageType.RemoveById, numericIds.Cast()); } public void PerformRefresh(ICacheRefresher refresher, params int[] numericIds) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); if (numericIds == null || numericIds.Length == 0) return; Deliver(refresher, MessageType.RefreshById, numericIds.Cast()); } public void PerformRefresh(ICacheRefresher refresher, params Guid[] guidIds) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); if (guidIds == null || guidIds.Length == 0) return; Deliver(refresher, MessageType.RefreshById, guidIds.Cast()); } public void PerformRefreshAll(ICacheRefresher refresher) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); Deliver(refresher, MessageType.RefreshAll); } //public void PerformNotify(ICacheRefresher refresher, object payload) //{ // if (servers == null) throw new ArgumentNullException("servers"); // if (refresher == null) throw new ArgumentNullException("refresher"); // Deliver(refresher, payload); //} #endregion #region Deliver protected void DeliverLocal(ICacheRefresher refresher, TPayload[] payload) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); Current.Logger.Debug("Invoking refresher {RefresherType} on local server for message type RefreshByPayload", refresher.GetType()); var payloadRefresher = refresher as IPayloadCacheRefresher; if (payloadRefresher == null) throw new InvalidOperationException("The cache refresher " + refresher.GetType() + " is not of type " + typeof(IPayloadCacheRefresher)); payloadRefresher.Refresh(payload); } /// /// Executes the non strongly typed on the local/current server /// /// /// /// /// /// /// Since this is only for non strongly typed it will throw for message types that by instance /// protected void DeliverLocal(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); Current.Logger.Debug("Invoking refresher {RefresherType} on local server for message type {MessageType}", refresher.GetType(), messageType); switch (messageType) { case MessageType.RefreshAll: refresher.RefreshAll(); break; case MessageType.RefreshById: if (ids != null) foreach (var id in ids) { 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.RefreshByJson: var jsonRefresher = refresher as IJsonCacheRefresher; if (jsonRefresher == null) throw new InvalidOperationException("The cache refresher " + refresher.GetType() + " is not of type " + typeof(IJsonCacheRefresher)); jsonRefresher.Refresh(json); break; case MessageType.RemoveById: if (ids != null) foreach (var id in ids) { if (id is int) refresher.Remove((int) id); else throw new InvalidOperationException("The id must be an int."); } break; default: //case MessageType.RefreshByInstance: //case MessageType.RemoveByInstance: throw new NotSupportedException("Invalid message type " + messageType); } } /// /// Executes the strongly typed on the local/current server /// /// /// /// /// /// /// /// Since this is only for strongly typed it will throw for message types that are not by instance /// protected void DeliverLocal(ICacheRefresher refresher, MessageType messageType, Func getId, IEnumerable instances) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); Current.Logger.Debug("Invoking refresher {RefresherType} on local server for message type {MessageType}", refresher.GetType(), messageType); var typedRefresher = refresher as ICacheRefresher; switch (messageType) { case MessageType.RefreshAll: refresher.RefreshAll(); break; case MessageType.RefreshByInstance: if (typedRefresher == null) throw new InvalidOperationException("The refresher must be a typed refresher."); foreach (var instance in instances) typedRefresher.Refresh(instance); break; case MessageType.RemoveByInstance: if (typedRefresher == null) throw new InvalidOperationException("The cache refresher " + refresher.GetType() + " is not a typed refresher."); foreach (var instance in instances) typedRefresher.Remove(instance); break; default: //case MessageType.RefreshById: //case MessageType.RemoveById: //case MessageType.RefreshByJson: throw new NotSupportedException("Invalid message type " + messageType); } } //protected void DeliverLocal(ICacheRefresher refresher, object payload) //{ // if (refresher == null) throw new ArgumentNullException("refresher"); // Current.Logger.Debug("Invoking refresher {0} on local server for message type Notify", // () => refresher.GetType()); // refresher.Notify(payload); //} protected abstract void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null); //protected abstract void DeliverRemote(ICacheRefresher refresher, object payload); protected virtual void Deliver(ICacheRefresher refresher, TPayload[] payload) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); // deliver local DeliverLocal(refresher, payload); // distribute? if (RequiresDistributed(refresher, MessageType.RefreshByJson) == false) return; // deliver remote var json = JsonConvert.SerializeObject(payload); DeliverRemote(refresher, MessageType.RefreshByJson, null, json); } protected virtual void Deliver(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); var idsA = ids?.ToArray(); // deliver local DeliverLocal(refresher, messageType, idsA, json); // distribute? if (RequiresDistributed(refresher, messageType) == false) return; // deliver remote DeliverRemote(refresher, messageType, idsA, json); } protected virtual void Deliver(ICacheRefresher refresher, MessageType messageType, Func getId, IEnumerable instances) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); var instancesA = instances.ToArray(); // deliver local DeliverLocal(refresher, messageType, getId, instancesA); // distribute? if (RequiresDistributed(refresher, messageType) == false) return; // deliver remote // map ByInstance to ById as there's no remote instances if (messageType == MessageType.RefreshByInstance) messageType = MessageType.RefreshById; if (messageType == MessageType.RemoveByInstance) messageType = MessageType.RemoveById; // convert instances to identifiers var idsA = instancesA.Select(getId).ToArray(); DeliverRemote(refresher, messageType, idsA); } //protected virtual void Deliver(ICacheRefresher refresher, object payload) //{ // if (servers == null) throw new ArgumentNullException("servers"); // if (refresher == null) throw new ArgumentNullException("refresher"); // var serversA = servers.ToArray(); // // deliver local // DeliverLocal(refresher, payload); // // distribute? // if (RequiresDistributed(serversA, refresher, messageType) == false) // return; // // deliver remote // DeliverRemote(serversA, refresher, payload); //} #endregion } }