This commit is contained in:
Morten Christensen
2013-02-12 12:49:00 -01:00
53 changed files with 1326 additions and 415 deletions

View File

@@ -14,6 +14,7 @@ using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Publishing;
using Umbraco.Core.Macros;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
using MigrationsVersionFourNineZero = Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero;
namespace Umbraco.Core
@@ -165,6 +166,16 @@ namespace Umbraco.Core
/// </summary>
protected virtual void InitializeResolvers()
{
//by default we'll use the standard configuration based sync
ServerRegistrarResolver.Current = new ServerRegistrarResolver(
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());

View File

@@ -9,25 +9,6 @@ namespace Umbraco.Core.Publishing
public abstract class BasePublishingStrategy : IPublishingStrategy
{
internal abstract Attempt<PublishStatus> PublishInternal(IContent content, int userId);
/// <summary>
/// Publishes a list of content items
/// </summary>
/// <param name="content"></param>
/// <param name="userId"></param>
/// <param name="includeUnpublishedDocuments">
/// By default this is set to true which means that it will publish any content item in the list that is completely unpublished and
/// not visible on the front-end. If set to false, this will only publish content that is live on the front-end but has new versions
/// that have yet to be published.
/// </param>
/// <param name="validateContent">If true this will validate each content item before trying to publish it, if validation fails it will not be published.</param>
/// <returns></returns>
internal abstract IEnumerable<Attempt<PublishStatus>> PublishWithChildrenInternal(
IEnumerable<IContent> content, int userId, bool includeUnpublishedDocuments = true, bool validateContent = false);
internal abstract IEnumerable<Attempt<PublishStatus>> UnPublishInternal(IEnumerable<IContent> content, int userId);
public abstract bool Publish(IContent content, int userId);
public abstract bool PublishWithChildren(IEnumerable<IContent> content, int userId);
public abstract bool UnPublish(IContent content, int userId);

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Core.Publishing
/// </summary>
/// <param name="content"><see cref="IContent"/> to publish</param>
/// <param name="userId">Id of the User issueing the publish operation</param>
internal override Attempt<PublishStatus> PublishInternal(IContent content, int userId)
internal Attempt<PublishStatus> PublishInternal(IContent content, int userId)
{
if (Publishing.IsRaisedEventCancelled(new PublishEventArgs<IContent>(content), this))
{
@@ -85,8 +85,7 @@ namespace Umbraco.Core.Publishing
/// By default this is set to true which means that it will publish any content item in the list that is completely unpublished and
/// not visible on the front-end. If set to false, this will only publish content that is live on the front-end but has new versions
/// that have yet to be published.
/// </param>
/// <param name="validateContent">If true this will validate each content item before trying to publish it, if validation fails it will not be published.</param>
/// </param>
/// <returns></returns>
/// <remarks>
/// This method becomes complex once we start to be able to cancel events or stop publishing a content item in any way because if a
@@ -99,8 +98,8 @@ namespace Umbraco.Core.Publishing
/// level and so on. If we detect that the above rule applies when the document publishing is cancelled we'll add it to the list of
/// parentsIdsCancelled so that it's children don't get published.
/// </remarks>
internal override IEnumerable<Attempt<PublishStatus>> PublishWithChildrenInternal(
IEnumerable<IContent> content, int userId, bool includeUnpublishedDocuments = true, bool validateContent = false)
internal IEnumerable<Attempt<PublishStatus>> PublishWithChildrenInternal(
IEnumerable<IContent> content, int userId, bool includeUnpublishedDocuments = true)
{
var statuses = new List<Attempt<PublishStatus>>();
@@ -163,7 +162,7 @@ namespace Umbraco.Core.Publishing
}
//Check if the content is valid if the flag is set to check
if (validateContent && !item.IsValid())
if (!item.IsValid())
{
LogHelper.Info<PublishingStrategy>(
string.Format("Content '{0}' with Id '{1}' will not be published because some of it's content is not passing validation rules.",
@@ -322,7 +321,7 @@ namespace Umbraco.Core.Publishing
/// <param name="content">An enumerable list of <see cref="IContent"/></param>
/// <param name="userId">Id of the User issueing the unpublish operation</param>
/// <returns>A list of publish statuses</returns>
internal override IEnumerable<Attempt<PublishStatus>> UnPublishInternal(IEnumerable<IContent> content, int userId)
internal IEnumerable<Attempt<PublishStatus>> UnPublishInternal(IEnumerable<IContent> content, int userId)
{
var result = new List<Attempt<PublishStatus>>();

View File

@@ -24,7 +24,7 @@ namespace Umbraco.Core.Services
public class ContentService : IContentService
{
private readonly IDatabaseUnitOfWorkProvider _uowProvider;
private readonly BasePublishingStrategy _publishingStrategy;
private readonly IPublishingStrategy _publishingStrategy;
private readonly RepositoryFactory _repositoryFactory;
public ContentService()
@@ -43,23 +43,16 @@ namespace Umbraco.Core.Services
: this(provider, repositoryFactory, new PublishingStrategy())
{ }
[Obsolete("This contructor is no longer supported, use the other constructor accepting a BasePublishginStrategy object instead")]
public ContentService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, IPublishingStrategy publishingStrategy)
{
_uowProvider = provider;
_publishingStrategy = publishingStrategy as BasePublishingStrategy;
if (_publishingStrategy == null)
throw new InvalidOperationException("publishingStrategy must be an instance of " + typeof(BasePublishingStrategy).Name);
if (provider == null) throw new ArgumentNullException("provider");
if (repositoryFactory == null) throw new ArgumentNullException("repositoryFactory");
if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy");
_uowProvider = provider;
_publishingStrategy = publishingStrategy;
_repositoryFactory = repositoryFactory;
}
public ContentService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, BasePublishingStrategy publishingStrategy)
{
_uowProvider = provider;
_publishingStrategy = publishingStrategy;
_repositoryFactory = repositoryFactory;
}
/// <summary>
/// Creates an <see cref="IContent"/> object using the alias of the <see cref="IContentType"/>
/// that this Content is based on.
@@ -477,7 +470,7 @@ namespace Umbraco.Core.Services
/// <returns>True if publishing succeeded, otherwise False</returns>
public bool RePublishAll(int userId = 0)
{
return RePublishAllDo(false, userId);
return RePublishAllDo(userId);
}
/// <summary>
@@ -488,7 +481,7 @@ namespace Umbraco.Core.Services
/// <returns>True if publishing succeeded, otherwise False</returns>
public bool Publish(IContent content, int userId = 0)
{
var result = SaveAndPublishDo(content, false, userId);
var result = SaveAndPublishDo(content, userId);
return result.Success;
}
@@ -500,7 +493,7 @@ namespace Umbraco.Core.Services
/// <returns>True if publishing succeeded, otherwise False</returns>
public bool PublishWithChildren(IContent content, int userId = 0)
{
var result = PublishWithChildrenDo(content, false, userId);
var result = PublishWithChildrenDo(content, userId);
//This used to just return false only when the parent content failed, otherwise would always return true so we'll
// do the same thing for the moment
@@ -527,7 +520,7 @@ namespace Umbraco.Core.Services
/// <returns>True if publishing succeeded, otherwise False</returns>
public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true)
{
var result = SaveAndPublishDo(content, false, userId, raiseEvents);
var result = SaveAndPublishDo(content, userId, raiseEvents);
return result.Success;
}
@@ -1063,67 +1056,41 @@ namespace Umbraco.Core.Services
}
#region Internal Methods
/// <summary>
/// Internal method to Re-Publishes all Content for legacy purposes.
/// </summary>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="omitCacheRefresh">Optional boolean to avoid having the cache refreshed when calling this RePublish method. By default this method will not update the cache.</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
internal bool RePublishAll(bool omitCacheRefresh = true, int userId = 0)
{
return RePublishAllDo(omitCacheRefresh, userId);
}
/// <summary>
/// Internal method that Publishes a single <see cref="IContent"/> object for legacy purposes.
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish</param>
/// <param name="omitCacheRefresh">Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will not update the cache.</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
internal Attempt<PublishStatus> Publish(IContent content, bool omitCacheRefresh = true, int userId = 0)
internal Attempt<PublishStatus> PublishInternal(IContent content, int userId = 0)
{
return SaveAndPublishDo(content, omitCacheRefresh, userId);
return SaveAndPublishDo(content, userId);
}
/// <summary>
/// Internal method that Publishes a <see cref="IContent"/> object and all its children for legacy purposes.
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish along with its children</param>
/// <param name="omitCacheRefresh">Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will not update the cache.</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="includeUnpublished">If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published</param>
/// <param name="validateContent">If true this will validate the content before publishing</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
internal IEnumerable<Attempt<PublishStatus>> PublishWithChildren(
IContent content, bool omitCacheRefresh = true, int userId = 0, bool includeUnpublished = false, bool validateContent = false)
internal IEnumerable<Attempt<PublishStatus>> PublishWithChildrenInternal(
IContent content, int userId = 0, bool includeUnpublished = false)
{
return PublishWithChildrenDo(content, omitCacheRefresh, userId, includeUnpublished, validateContent);
}
/// <summary>
/// Internal method that UnPublishes a single <see cref="IContent"/> object for legacy purposes.
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish</param>
/// <param name="omitCacheRefresh">Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will not update the cache.</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if unpublishing succeeded, otherwise False</returns>
internal bool UnPublish(IContent content, bool omitCacheRefresh = true, int userId = 0)
{
return UnPublishDo(content, omitCacheRefresh, userId);
return PublishWithChildrenDo(content, userId, includeUnpublished);
}
/// <summary>
/// Saves and Publishes a single <see cref="IContent"/> object
/// </summary>
/// <param name="content">The <see cref="IContent"/> to save and publish</param>
/// <param name="omitCacheRefresh">Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will not update the cache.</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
internal Attempt<PublishStatus> SaveAndPublish(IContent content, bool omitCacheRefresh = true, int userId = 0, bool raiseEvents = true)
internal Attempt<PublishStatus> SaveAndPublishInternal(IContent content, int userId = 0, bool raiseEvents = true)
{
return SaveAndPublishDo(content, omitCacheRefresh, userId, raiseEvents);
return SaveAndPublishDo(content, userId, raiseEvents);
}
/// <summary>
@@ -1150,9 +1117,8 @@ namespace Umbraco.Core.Services
/// Re-Publishes all Content
/// </summary>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="omitCacheRefresh">Optional boolean to avoid having the cache refreshed when calling this RePublish method. By default this method will update the cache.</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
private bool RePublishAllDo(bool omitCacheRefresh = false, int userId = 0)
private bool RePublishAllDo(int userId = 0)
{
var list = new List<IContent>();
var updated = new List<IContent>();
@@ -1198,9 +1164,8 @@ namespace Umbraco.Core.Services
: Convert.ToInt32(uow.Database.Insert(poco));
}
}
//Updating content to published state is finished, so we fire event through PublishingStrategy to have cache updated
if (omitCacheRefresh == false)
_publishingStrategy.PublishingFinalized(updated, true);
_publishingStrategy.PublishingFinalized(updated, true);
}
Audit.Add(AuditTypes.Publish, "RePublish All performed by user", userId, -1);
@@ -1212,17 +1177,15 @@ namespace Umbraco.Core.Services
/// Publishes a <see cref="IContent"/> object and all its children
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish along with its children</param>
/// <param name="omitCacheRefresh">Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will update the cache.</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="includeUnpublished">If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published</param>
/// <param name="validateContent">If set to true will ensure the content is valid before publishing</param>
/// <param name="includeUnpublished">If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published</param>
/// <returns>
/// A list of publish statues. If the parent document is not valid or cannot be published because it's parent(s) is not published
/// then the list will only contain one status item, otherwise it will contain status items for it and all of it's descendants that
/// are to be published.
/// </returns>
private IEnumerable<Attempt<PublishStatus>> PublishWithChildrenDo(
IContent content, bool omitCacheRefresh = false, int userId = 0, bool includeUnpublished = false, bool validateContent = false)
IContent content, int userId = 0, bool includeUnpublished = false)
{
if (content == null) throw new ArgumentNullException("content");
@@ -1257,8 +1220,10 @@ namespace Umbraco.Core.Services
list.Add(content); //include parent item
list.AddRange(GetDescendants(content));
var internalStrategy = (PublishingStrategy)_publishingStrategy;
//Publish and then update the database with new status
var publishedOutcome = _publishingStrategy.PublishWithChildrenInternal(list, userId, includeUnpublished, validateContent).ToArray();
var publishedOutcome = internalStrategy.PublishWithChildrenInternal(list, userId, includeUnpublished).ToArray();
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
@@ -1288,8 +1253,7 @@ namespace Umbraco.Core.Services
}
}
//Save xml to db and call following method to fire event:
if (omitCacheRefresh == false)
_publishingStrategy.PublishingFinalized(updated, false);
_publishingStrategy.PublishingFinalized(updated, false);
Audit.Add(AuditTypes.Publish, "Publish with Children performed by user", userId, content.Id);
@@ -1334,11 +1298,10 @@ namespace Umbraco.Core.Services
/// Saves and Publishes a single <see cref="IContent"/> object
/// </summary>
/// <param name="content">The <see cref="IContent"/> to save and publish</param>
/// <param name="omitCacheRefresh">Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will update the cache.</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
private Attempt<PublishStatus> SaveAndPublishDo(IContent content, bool omitCacheRefresh = false, int userId = 0, bool raiseEvents = true)
private Attempt<PublishStatus> SaveAndPublishDo(IContent content, int userId = 0, bool raiseEvents = true)
{
if(raiseEvents)
{
@@ -1372,8 +1335,9 @@ namespace Umbraco.Core.Services
publishStatus.StatusType = PublishStatusType.FailedContentInvalid;
}
var internalStrategy = (PublishingStrategy) _publishingStrategy;
//Publish and then update the database with new status
var publishResult = _publishingStrategy.PublishInternal(content, userId);
var publishResult = internalStrategy.PublishInternal(content, userId);
//set our publish status to the publish result
publishStatus.StatusType = publishResult.Result.StatusType;
@@ -1430,13 +1394,13 @@ namespace Umbraco.Core.Services
Saved.RaiseEvent(new SaveEventArgs<IContent>(content, false), this);
//Save xml to db and call following method to fire event through PublishingStrategy to update cache
if (published && omitCacheRefresh == false)
if (published)
{
_publishingStrategy.PublishingFinalized(content);
}
//We need to check if children and their publish state to ensure that we 'republish' content that was previously published
if (published && omitCacheRefresh == false && previouslyPublished == false && HasChildren(content.Id))
if (published && previouslyPublished == false && HasChildren(content.Id))
{
var descendants = GetPublishedDescendants(content);

View File

@@ -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
{
/// <summary>
/// A registrar that uses the legacy xml configuration in umbracoSettings to get a list of defined server nodes
/// </summary>
internal class ConfigServerRegistrar : IServerRegistrar
{
private readonly XmlNode _xmlServers;
public ConfigServerRegistrar()
: this(UmbracoSettings.DistributionServers)
{
}
internal ConfigServerRegistrar(XmlNode xmlServers)
{
_xmlServers = xmlServers;
}
private List<IServerRegistration> _addresses;
public IEnumerable<IServerRegistration> Registrations
{
get
{
if (_addresses == null)
{
_addresses = new List<IServerRegistration>();
var nodes = _xmlServers.SelectNodes("./server");
foreach (XmlNode n in nodes)
{
_addresses.Add(new ConfigServerRegistration(n));
}
}
return _addresses;
}
}
}
}

View File

@@ -0,0 +1,29 @@
using System.Xml;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
namespace Umbraco.Core.Sync
{
/// <summary>
/// A server registration based on the legacy umbraco xml configuration in umbracoSettings
/// </summary>
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; }
}
}

View File

@@ -0,0 +1,401 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading;
using System.Web.Script.Serialization;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using umbraco.interfaces;
namespace Umbraco.Core.Sync
{
/// <summary>
/// The default server messenger that uses web services to keep servers in sync
/// </summary>
internal class DefaultServerMessenger : IServerMessenger
{
private readonly string _login;
private readonly string _password;
private readonly bool _useDistributedCalls;
/// <summary>
/// Without a username/password all distribuion will be disabled
/// </summary>
internal DefaultServerMessenger()
{
_useDistributedCalls = false;
}
/// <summary>
/// Distribution will be enabled based on the umbraco config setting.
/// </summary>
/// <param name="login"></param>
/// <param name="password"></param>
internal DefaultServerMessenger(string login, string password)
{
_useDistributedCalls = UmbracoSettings.UseDistributedCalls;
_login = login;
_password = password;
}
public void PerformRefresh<T>(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher,Func<T, int> getNumericId, params T[] instances)
{
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
//copy local
var idGetter = getNumericId;
MessageSeversForManyObjects(servers, refresher, MessageType.RefreshById,
x => idGetter(x),
instances);
}
public void PerformRefresh<T>(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, Func<T, Guid> getGuidId, params T[] instances)
{
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
//copy local
var idGetter = getGuidId;
MessageSeversForManyObjects(servers, refresher, MessageType.RefreshById,
x => idGetter(x),
instances);
}
public void PerformRemove<T>(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, Func<T, int> getNumericId, params T[] instances)
{
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
//copy local
var idGetter = getNumericId;
MessageSeversForManyObjects(servers, refresher, MessageType.RemoveById,
x => idGetter(x),
instances);
}
public void PerformRemove(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, params int[] numericIds)
{
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
MessageSeversForManyIds(servers, refresher, MessageType.RemoveById, numericIds.Cast<object>());
}
public void PerformRefresh(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, params int[] numericIds)
{
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
MessageSeversForManyIds(servers, refresher, MessageType.RefreshById, numericIds.Cast<object>());
}
public void PerformRefresh(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, params Guid[] guidIds)
{
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
MessageSeversForManyIds(servers, refresher, MessageType.RefreshById, guidIds.Cast<object>());
}
public void PerformRefreshAll(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher)
{
MessageSeversForManyIds(servers, refresher, MessageType.RefreshAll, Enumerable.Empty<object>().ToArray());
}
private void InvokeMethodOnRefresherInstance<T>(ICacheRefresher refresher, MessageType dispatchType, Func<T, object> getId, IEnumerable<T> instances)
{
if (refresher == null) throw new ArgumentNullException("refresher");
var stronglyTypedRefresher = refresher as ICacheRefresher<T>;
foreach (var instance in instances)
{
//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 (stronglyTypedRefresher != null)
{
stronglyTypedRefresher.Refresh(instance);
}
else
{
var id = getId(instance);
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:
if (stronglyTypedRefresher != null)
{
stronglyTypedRefresher.Remove(instance);
}
else
{
var id = getId(instance);
refresher.Refresh((int)id);
}
break;
}
}
}
private void InvokeMethodOnRefresherInstance(ICacheRefresher refresher, MessageType dispatchType, IEnumerable<object> ids)
{
if (refresher == null) throw new ArgumentNullException("refresher");
//if it is a refresh all we'll do it here since ids will be null or empty
if (dispatchType == MessageType.RefreshAll)
{
refresher.RefreshAll();
}
else
{
foreach (var id in ids)
{
//if we are not, then just invoke the call on the cache refresher
switch (dispatchType)
{
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 MessageSeversForManyObjects<T>(
IEnumerable<IServerRegistration> servers,
ICacheRefresher refresher,
MessageType dispatchType,
Func<T, object> getId,
IEnumerable<T> instances)
{
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, getId, instances);
return;
}
//if we are distributing calls then we'll need to do it by id
MessageSeversForManyIds(servers, refresher, dispatchType, instances.Select(getId));
}
private void MessageSeversForManyIds(
IEnumerable<IServerRegistration> servers,
ICacheRefresher refresher,
MessageType dispatchType,
IEnumerable<object> ids)
{
if (servers == null) throw new ArgumentNullException("servers");
if (refresher == null) throw new ArgumentNullException("refresher");
Type arrayType = null;
foreach (var id in ids)
{
if (!(id is int) && (!(id is Guid)))
throw new ArgumentException("The id must be either an int or a Guid");
if (arrayType == null)
arrayType = id.GetType();
if (arrayType != id.GetType())
throw new ArgumentException("The array must contain the same type of " + arrayType);
}
//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, ids);
return;
}
//We are using distributed calls, so lets make them...
try
{
using (var cacheRefresher = new ServerSyncWebServiceClient())
{
var asyncResultsList = new List<IAsyncResult>();
LogStartDispatch();
// Go through each configured node submitting a request asynchronously
foreach (var n in servers)
{
//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:
if (arrayType == typeof(int))
{
var serializer = new JavaScriptSerializer();
var jsonIds = serializer.Serialize(ids.Cast<int>().ToArray());
//we support bulk loading of Integers
var result = cacheRefresher.BeginRefreshByIds(refresher.UniqueIdentifier, jsonIds, _login, _password, null, null);
asyncResultsList.Add(result);
}
else
{
//we don't currently support bulk loading of GUIDs (not even sure if we have any Guid ICacheRefreshers)
//so we'll just iterate
asyncResultsList.AddRange(
ids.Select(i => cacheRefresher.BeginRefreshByGuid(
refresher.UniqueIdentifier, (Guid) i, _login, _password, null, null)));
}
break;
case MessageType.RemoveById:
//we don't currently support bulk removing so we'll iterate
asyncResultsList.AddRange(
ids.Select(i => cacheRefresher.BeginRemoveById(
refresher.UniqueIdentifier, (int)i, _login, _password, null, null)));
break;
}
}
List<WaitHandle> 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 (arrayType == typeof(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<IAsyncResult> GetAsyncResults(List<IAsyncResult> asyncResultsList, out List<WaitHandle> waitHandlesList)
{
var asyncResults = asyncResultsList.ToArray();
waitHandlesList = new List<WaitHandle>();
foreach (var asyncResult in asyncResults)
{
waitHandlesList.Add(asyncResult.AsyncWaitHandle);
}
return asyncResults;
}
private void LogDispatchBatchError(Exception ee)
{
LogHelper.Error<DefaultServerMessenger>("Error refreshing distributed list", ee);
}
private void LogDispatchBatchResult(int errorCount)
{
LogHelper.Debug<DefaultServerMessenger>(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<DefaultServerMessenger>("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<DefaultServerMessenger>("Error refreshing a node in the distributed list, URI attempted: " + url, ex);
}
private void LogStartDispatch()
{
LogHelper.Info<DefaultServerMessenger>("Submitting calls to distributed servers");
}
}
}

View File

@@ -0,0 +1,18 @@
using umbraco.interfaces;
namespace Umbraco.Core.Sync
{
/// <summary>
/// Strongly type cache refresher that is able to refresh cache of real instances of objects as well as IDs
/// </summary>
/// <typeparam name="T"></typeparam>
/// <remarks>
/// This is much better for performance when we're not running in a load balanced environment so we can refresh the cache
/// against a already resolved object instead of looking the object back up by id.
/// </remarks>
interface ICacheRefresher<T> : ICacheRefresher
{
void Refresh(T instance);
void Remove(T instance);
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using umbraco.interfaces;
namespace Umbraco.Core.Sync
{
/// <summary>
/// Defines a server messenger for server sync and distrubuted cache
/// </summary>
internal interface IServerMessenger
{
/// <summary>
/// Performs a sync against all instance objects
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="servers">The servers to sync against</param>
/// <param name="refresher"></param>
/// <param name="getNumericId">A delegate to return the Id for each instance to be used to sync to other servers</param>
/// <param name="instances"></param>
void PerformRefresh<T>(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, Func<T, int> getNumericId, params T[] instances);
/// <summary>
/// Performs a sync against all instance objects
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="servers">The servers to sync against</param>
/// <param name="refresher"></param>
/// <param name="getGuidId">A delegate to return the Id for each instance to be used to sync to other servers</param>
/// <param name="instances"></param>
void PerformRefresh<T>(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, Func<T, Guid> getGuidId, params T[] instances);
/// <summary>
/// Removes the cache for the specified items
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="servers"></param>
/// <param name="refresher"></param>
/// <param name="getNumericId">A delegate to return the Id for each instance to be used to sync to other servers</param>
/// <param name="instances"></param>
void PerformRemove<T>(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, Func<T, int> getNumericId, params T[] instances);
/// <summary>
/// Removes the cache for the specified items
/// </summary>
/// <param name="servers"></param>
/// <param name="refresher"></param>
/// <param name="numericIds"></param>
void PerformRemove(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, params int[] numericIds);
/// <summary>
/// Performs a sync against all Ids
/// </summary>
/// <param name="servers">The servers to sync against</param>
/// <param name="refresher"></param>
/// <param name="numericIds"></param>
void PerformRefresh(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, params int[] numericIds);
/// <summary>
/// Performs a sync against all Ids
/// </summary>
/// <param name="servers">The servers to sync against</param>
/// <param name="refresher"></param>
/// <param name="guidIds"></param>
void PerformRefresh(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher, params Guid[] guidIds);
/// <summary>
/// Performs entire cache refresh for a specified refresher
/// </summary>
/// <param name="servers"></param>
/// <param name="refresher"></param>
void PerformRefreshAll(IEnumerable<IServerRegistration> servers, ICacheRefresher refresher);
}
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Umbraco.Core.Sync
{
/// <summary>
/// An interface to expose a list of server registrations for server syncing
/// </summary>
internal interface IServerRegistrar
{
IEnumerable<IServerRegistration> Registrations { get; }
}
}

View File

@@ -0,0 +1,10 @@
namespace Umbraco.Core.Sync
{
/// <summary>
/// An interface exposing a server address to use for server syncing
/// </summary>
internal interface IServerRegistration
{
string ServerAddress { get; }
}
}

View File

@@ -0,0 +1,12 @@
namespace Umbraco.Core.Sync
{
/// <summary>
/// The message type to be used for syncing across servers
/// </summary>
internal enum MessageType
{
RefreshAll,
RefreshById,
RemoveById
}
}

View File

@@ -0,0 +1,29 @@
using Umbraco.Core.ObjectResolution;
namespace Umbraco.Core.Sync
{
/// <summary>
/// A resolver to return the currently registered IServerMessenger object
/// </summary>
internal class ServerMessengerResolver : SingleObjectResolverBase<ServerMessengerResolver, IServerMessenger>
{
internal ServerMessengerResolver(IServerMessenger factory)
: base(factory)
{
}
/// <summary>
/// Can be used at runtime to set a custom IServerMessenger at app startup
/// </summary>
/// <param name="serverMessenger"></param>
public void SetServerMessenger(IServerMessenger serverMessenger)
{
Value = serverMessenger;
}
public IServerMessenger Messenger
{
get { return Value; }
}
}
}

View File

@@ -0,0 +1,31 @@
using Umbraco.Core.ObjectResolution;
namespace Umbraco.Core.Sync
{
/// <summary>
/// The resolver to return the currently registered IServerRegistrar object
/// </summary>
internal class ServerRegistrarResolver : SingleObjectResolverBase<ServerRegistrarResolver, IServerRegistrar>
{
internal ServerRegistrarResolver(IServerRegistrar factory)
: base(factory)
{
}
/// <summary>
/// Can be used at runtime to set a custom IServerRegistrar at app startup
/// </summary>
/// <param name="serverRegistrar"></param>
public void SetServerRegistrar(IServerRegistrar serverRegistrar)
{
Value = serverRegistrar;
}
public IServerRegistrar Registrar
{
get { return Value; }
}
}
}

View File

@@ -0,0 +1,193 @@
using System.Collections.Generic;
using System.Web.Services;
using Umbraco.Core.IO;
namespace Umbraco.Core.Sync
{
/// <summary>
/// The client Soap service for making distrubuted cache calls between servers
/// </summary>
[WebServiceBinding(Name = "CacheRefresherSoap", Namespace = "http://umbraco.org/webservices/")]
internal class ServerSyncWebServiceClient : System.Web.Services.Protocols.SoapHttpClientProtocol
{
/// <remarks/>
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";
}
/// <remarks/>
[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});
}
/// <remarks/>
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);
}
/// <remarks/>
public void EndRefreshAll(System.IAsyncResult asyncResult)
{
this.EndInvoke(asyncResult);
}
/// <remarks/>
[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});
}
/// <remarks/>
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);
}
/// <remarks/>
public void EndRefreshByGuid(System.IAsyncResult asyncResult)
{
this.EndInvoke(asyncResult);
}
/// <remarks/>
[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});
}
/// <remarks/>
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);
}
/// <remarks/>
public void EndRefreshById(System.IAsyncResult asyncResult)
{
this.EndInvoke(asyncResult);
}
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByIds",
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 RefreshByIds(System.Guid uniqueIdentifier, string jsonIds, string Login, string Password)
{
this.Invoke("RefreshByIds", new object[] {
uniqueIdentifier,
jsonIds,
Login,
Password});
}
/// <remarks/>
public System.IAsyncResult BeginRefreshByIds(System.Guid uniqueIdentifier, string jsonIds, string Login, string Password, System.AsyncCallback callback, object asyncState)
{
return this.BeginInvoke("RefreshByIds", new object[] {
uniqueIdentifier,
jsonIds,
Login,
Password}, callback, asyncState);
}
/// <remarks/>
public void EndRefreshByIds(System.IAsyncResult asyncResult)
{
this.EndInvoke(asyncResult);
}
/// <remarks/>
[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});
}
/// <remarks/>
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);
}
/// <remarks/>
public void EndRemoveById(System.IAsyncResult asyncResult)
{
this.EndInvoke(asyncResult);
}
/// <remarks/>
[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]));
}
/// <remarks/>
public System.IAsyncResult BeginGetRefreshers(string Login, string Password, System.AsyncCallback callback, object asyncState)
{
return this.BeginInvoke("GetRefreshers", new object[] {
Login,
Password}, callback, asyncState);
}
/// <remarks/>
public System.Xml.XmlNode EndGetRefreshers(System.IAsyncResult asyncResult)
{
object[] results = this.EndInvoke(asyncResult);
return ((System.Xml.XmlNode)(results[0]));
}
}
}

View File

@@ -61,6 +61,7 @@
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Transactions" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Web.Helpers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.Helpers.dll</HintPath>
@@ -73,6 +74,7 @@
<Private>True</Private>
<HintPath>..\packages\Microsoft.AspNet.Razor.2.0.20715.0\lib\net40\System.Web.Razor.dll</HintPath>
</Reference>
<Reference Include="System.Web.Services" />
<Reference Include="System.Web.WebPages, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.dll</HintPath>
@@ -667,6 +669,19 @@
<Compile Include="Services\MediaService.cs" />
<Compile Include="Services\ServiceContext.cs" />
<Compile Include="Services\UserService.cs" />
<Compile Include="Sync\DefaultServerMessenger.cs" />
<Compile Include="Sync\ICacheRefresher.cs" />
<Compile Include="Sync\ServerSyncWebServiceClient.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Sync\ConfigServerRegistration.cs" />
<Compile Include="Sync\IServerMessenger.cs" />
<Compile Include="Sync\IServerRegistrar.cs" />
<Compile Include="Sync\IServerRegistration.cs" />
<Compile Include="Sync\MessageType.cs" />
<Compile Include="Sync\ServerMessengerResolver.cs" />
<Compile Include="Sync\ServerRegistrarResolver.cs" />
<Compile Include="Sync\ConfigServerRegistrar.cs" />
<Compile Include="TypeExtensions.cs" />
<Compile Include="ReadLock.cs" />
<Compile Include="TypeFinder.cs" />