diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Brokers/SqlServer/SqlServerBroker.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Brokers/SqlServer/SqlServerBroker.cs new file mode 100644 index 000000000..6c4b00950 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Brokers/SqlServer/SqlServerBroker.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Linq; +using System.Security.Permissions; +using System.Threading.Tasks; +using System.Web; +using Orchard.Data; +using Orchard.Environment; +using Orchard.Environment.Configuration; +using Orchard.Environment.Extensions; +using Orchard.Logging; +using Orchard.MessageBus.Models; +using Orchard.MessageBus.Services; +using Orchard.Services; + +namespace Orchard.MessageBus.Brokers.SqlServer { + /// + /// A single connection is maintained, and each subscription will be triggered based on the channel it's listening to + /// + [OrchardFeature("Orchard.MessageBus.SqlServerServiceBroker")] + public class SqlServerBroker : IMessageBroker, IDisposable { + + private IWorker _worker; + private bool _initialized; + private object _synLock = new object(); + + private readonly Work> _messageRecordRepository; + private readonly Work _clock; + private readonly Func _workerFactory; + private readonly ShellSettings _shellSettings; + private readonly Work _hostNameProvider; + + public SqlServerBroker( + Work> messageRecordRepository, + Work clock, + Work hostNameProvider, + Func workerFactory, + ShellSettings shellSettings + ) { + _messageRecordRepository = messageRecordRepository; + _clock = clock; + _shellSettings = shellSettings; + _workerFactory = workerFactory; + _hostNameProvider = hostNameProvider; + + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public bool EnsureInitialized() { + lock (_synLock) { + if (!_initialized) { + try { + // call only once per connectionstring when appdomain starts up + Logger.Information("Starting SqlDependency."); + SqlDependency.Start(_shellSettings.DataConnectionString); + + _worker = _workerFactory(); + _worker.Work(); + + _initialized = true; + } + catch (Exception e) { + Logger.Error("The application doesn't have the permission to request notifications.", e); + } + } + + return _initialized; + } + } + + public void Subscribe(string channel, Action handler) { + if (!EnsureInitialized()) { + return; + } + + try { + lock (_synLock) { + _worker.RegisterHandler(channel, handler); + } + } + catch(Exception e) { + Logger.Error("An error occured while creating a Worker.", e); + } + } + + public void Publish(string channel, string message) { + if (!EnsureInitialized()) { + return; + } + + // clear old messages on publish to get a single worker + var oldMessages = _messageRecordRepository.Value + .Table + .Where(x => x.CreatedUtc <= _clock.Value.UtcNow.AddHours(-1)) + .ToList(); + + foreach (var messageRecord in oldMessages) { + _messageRecordRepository.Value.Delete(messageRecord); + } + + _messageRecordRepository.Value.Create( + new MessageRecord { + Channel = channel, + Message = message, + Publisher = _hostNameProvider.Value.GetHostName(), + CreatedUtc = _clock.Value.UtcNow + } + ); + } + + public void Dispose() { + // call only once per connectionstring when appdomain shuts down + if (!String.IsNullOrWhiteSpace(_shellSettings.DataConnectionString)) { + SqlDependency.Stop(_shellSettings.DataConnectionString); + } + } + + private string GetHostName() { + // use the current host and the process id as two servers could run on the same machine + return System.Net.Dns.GetHostName() + ":" + System.Diagnostics.Process.GetCurrentProcess().Id; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Brokers/SqlServer/Worker.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Brokers/SqlServer/Worker.cs new file mode 100644 index 000000000..4bac8d4ba --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Brokers/SqlServer/Worker.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlClient; +using System.Linq; +using System.Threading; +using System.Web; +using System.Web.Hosting; +using Orchard.Environment.Configuration; +using Orchard.Environment.Extensions; +using Orchard.Logging; +using Orchard.MessageBus.Models; +using Orchard.MessageBus.Services; + +namespace Orchard.MessageBus.Brokers.SqlServer { + public interface IWorker : IDependency { + void Work(); + void RegisterHandler(string channel, Action handler); + } + + [OrchardFeature("Orchard.MessageBus.SqlServerServiceBroker")] + public class Worker : IWorker, IRegisteredObject { + + private readonly ShellSettings _shellSettings; + private readonly IHostNameProvider _hostNameProvider; + + private SqlDependency _dependency; + + private static string commandText = "SELECT Id, Channel, Publisher, Message, CreatedUtc FROM dbo.{0}Orchard_MessageBus_MessageRecord WHERE Id > @Id"; + private static int lastMessageId = 0; + private bool _stopped; + + private Dictionary>> _handlers = new Dictionary>>(); + + public Worker(ShellSettings shellSettings, IHostNameProvider hostNameProvider) { + _hostNameProvider = hostNameProvider; + _shellSettings = shellSettings; + + var tablePrefix = _shellSettings.DataTablePrefix; + if (!String.IsNullOrWhiteSpace(tablePrefix)) { + tablePrefix += "_"; + } + + commandText = String.Format(commandText, tablePrefix); + + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public void Work() { + // exit loop if stop notification as been triggered + if (_stopped) { + return; + } + + try { + IEnumerable messages; + + // load and process existing messages + using (var connection = new SqlConnection(_shellSettings.DataConnectionString)) { + connection.Open(); + + var command = CreateCommand(connection); + messages = GetMessages(command); + } + + ProcessMessages(messages); + + // wait for new messages to be available + WaitForWork(); + + } + catch (Exception e) { + Logger.Error("An unexpected error occured while monitoring sql dependencies.", e); + } + } + + private void DoWork(object sender, SqlNotificationEventArgs eventArgs) { + Work(); + } + + private void WaitForWork() { + + using (var connection = new SqlConnection(_shellSettings.DataConnectionString)) { + connection.Open(); + + using (var command = CreateCommand(connection)) { + + // create a sql depdendency on the table we are monitoring + _dependency = new SqlDependency(command); + + // when new records are present, continue the thread + _dependency.OnChange += DoWork; + + // start monitoring the table + command.ExecuteNonQuery(); + } + } + } + + private void ProcessMessages(IEnumerable messages) { + + // if this is the first time it's executed we just need to get the highest Id + if (lastMessageId == 0) { + lastMessageId = messages.Max(x => x.Id); + return; + } + + // process the messages synchronously and in order of publication + foreach (var message in messages.OrderBy(x => x.Id)) { + + // save the latest message id so that next time the table is monitored + // we get notified for new messages + lastMessageId = message.Id; + + // only process handlers registered for the specific channel + List> channelHandlers; + if (_handlers.TryGetValue(message.Channel, out channelHandlers)) { + + var hostName = _hostNameProvider.GetHostName(); + + // execute subscription + foreach (var handler in channelHandlers) { + + // ignore messages sent by the current host + if (!message.Publisher.Equals(hostName, StringComparison.OrdinalIgnoreCase)) { + handler(message.Channel, message.Message); + } + + // stop processing other messages if stop has been required + if (_stopped) { + return; + } + } + } + } + } + + public void Stop(bool immediate) { + _stopped = true; + } + + public void RegisterHandler(string channel, Action handler) { + GetHandlersForChannel(channel).Add(handler); + } + + private List> GetHandlersForChannel(string channel) { + List> channelHandlers; + + if(!_handlers.TryGetValue(channel, out channelHandlers)) { + channelHandlers = new List>(); + _handlers.Add(channel, channelHandlers); + } + + return channelHandlers; + } + + public SqlCommand CreateCommand(SqlConnection connection) { + SqlCommand command = new SqlCommand(commandText, connection); + + SqlParameter param = new SqlParameter("@Id", SqlDbType.Int); + param.Direction = ParameterDirection.Input; + param.DbType = DbType.Int32; + param.Value = lastMessageId; + command.Parameters.Add(param); + + return command; + } + + public IEnumerable GetMessages(SqlCommand command) { + var result = new List(); + + try { + + using (var reader = command.ExecuteReader()) { + if (reader.HasRows) { + while (reader.Read()) { + result.Add(new MessageRecord { + Id = reader.GetInt32(0), + Channel = reader.GetString(1), + Publisher = reader.GetString(2), + Message = reader.GetString(3), + CreatedUtc = reader.GetDateTime(4) + }); + } + } + } + } + catch (Exception e) { + Logger.Error("Could not retreive Sql Broker messages.", e); + return Enumerable.Empty(); + } + + return result; + } + + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Handler/MessageBusHandler.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Handler/MessageBusHandler.cs new file mode 100644 index 000000000..296350c8d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Handler/MessageBusHandler.cs @@ -0,0 +1,9 @@ +using System.Linq; +using JetBrains.Annotations; +using Orchard.Data; +using Orchard.ContentManagement.Handlers; + +namespace Orchard.MessageBus.Handlers { + public class MessageBusHandler : ContentHandler { + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Models/MessageRecord.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Models/MessageRecord.cs new file mode 100644 index 000000000..bd6924662 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Models/MessageRecord.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Orchard.Data.Conventions; +using Orchard.Environment.Extensions; + +namespace Orchard.MessageBus.Models { + + [OrchardFeature("Orchard.MessageBus.SqlServerServiceBroker")] + public class MessageRecord { + + public virtual int Id { get; set; } + public virtual string Publisher { get; set; } + public virtual string Channel { get; set; } + public virtual DateTime CreatedUtc { get; set; } + + [StringLengthMax] + public virtual string Message { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Module.txt b/src/Orchard.Web/Modules/Orchard.MessageBus/Module.txt new file mode 100644 index 000000000..96dfd8582 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Module.txt @@ -0,0 +1,27 @@ +Name: Orchard.MessageBus +AntiForgery: enabled +Author: The Orchard Team +Website: http://orchardproject.net +Version: 1.0 +OrchardVersion: 1.0 +Description: Provides communication APIs for server farms. +Features: + Orchard.MessageBus: + Name: Message Bus + Description: Reusable API abstractions to communicate in a server farm. + Category: Hosting + Orchard.MessageBus.DistributedSignals: + Name: Distributed Signals + Description: Distribute signals cache invalidation calls. + Dependencies: Orchard.MessageBus + Category: Hosting + Orchard.MessageBus.SqlServerServiceBroker: + Name: SQL Server Service Broker + Description: A message bus implementation using SQL Server Service Broker. + Dependencies: Orchard.MessageBus + Category: Hosting + Orchard.MessageBus.DistributedShellRestart + Name: Distributed Shell Restart + Description: Distribute shell restarts. + Dependencies: Orchard.MessageBus + Category: Hosting diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Orchard.MessageBus.csproj b/src/Orchard.Web/Modules/Orchard.MessageBus/Orchard.MessageBus.csproj new file mode 100644 index 000000000..600bf4d12 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Orchard.MessageBus.csproj @@ -0,0 +1,154 @@ + + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {ED715544-E649-4F48-B8EE-9368C41C3AC0} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Orchard.MessageBus + Orchard.MessageBus + v4.5.1 + false + + + 4.0 + + + false + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + false + + + pdbonly + true + bin\ + TRACE + prompt + 4 + AllRules.ruleset + false + + + + + False + ..\..\..\..\lib\nhibernate\NHibernate.dll + + + + + 3.5 + + + + False + ..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll + + + + + + + + + + + + + + + + + + + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} + Orchard.Framework + + + {9916839C-39FC-4CEB-A5AF-89CA7E87119F} + Orchard.Core + + + + + + + + + + + + + + + + + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + $(ProjectDir)\..\Manifests + + + + + + + + + + + + False + True + 45979 + / + + + False + True + http://orchard.codeplex.com + False + + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Properties/AssemblyInfo.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..220f9c927 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Orchard.MessageBus")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Orchard")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5b79fe08-1e5b-44ab-9323-2c08a7e461e5")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Scripts/Web.config b/src/Orchard.Web/Modules/Orchard.MessageBus/Scripts/Web.config new file mode 100644 index 000000000..817198995 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Scripts/Web.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DefaultMessageBus.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DefaultMessageBus.cs new file mode 100644 index 000000000..876b87d0a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DefaultMessageBus.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Threading.Tasks; + +namespace Orchard.MessageBus.Services { + public class DefaultMessageBus : IMessageBus { + private readonly IMessageBroker _messageBroker; + + public DefaultMessageBus(IEnumerable messageBrokers) { + _messageBroker = messageBrokers.FirstOrDefault(); + } + + public void Subscribe(string channel, Action handler) { + if (_messageBroker == null) { + return; + } + + _messageBroker.Subscribe(channel, handler); + } + + public void Publish(string channel, string message) { + if (_messageBroker == null) { + return; + } + + _messageBroker.Publish(channel, message); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedShellStarter.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedShellStarter.cs new file mode 100644 index 000000000..97bef5f20 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedShellStarter.cs @@ -0,0 +1,51 @@ +using System.Linq; +using Orchard.Caching; +using Orchard.Environment; +using Orchard.Environment.Configuration; +using Orchard.Environment.Extensions; + +namespace Orchard.MessageBus.Services { + + public interface IDistributedShellStarter : ISingletonDependency { + + } + + [OrchardFeature("Orchard.MessageBus.DistributedShellRestart")] + public class DistributedShellStarter : IDistributedShellStarter, IOrchardShellEvents { + private readonly IWorkContextAccessor _workContextAccessor; + + private readonly IMessageBus _messageBus; + + public readonly static string Channel = "ShellChanged"; + + public DistributedShellStarter(IMessageBus messageBus, IWorkContextAccessor workContextAccessor) { + _messageBus = messageBus; + _workContextAccessor = workContextAccessor; + } + + public void Activated() { + _messageBus.Subscribe(Channel, (channel, message) => { + // todo: this only handles changed tenants, we should consider handling started and stopped tenants + + using (var scope = _workContextAccessor.CreateWorkContextScope()) { + var shellSettings = scope.Resolve(); + if (shellSettings != null) { + + // todo: this doesn't work as the new tenants list is lost right after + var shellSettingsManagerEventHandler = scope.Resolve(); + shellSettingsManagerEventHandler.Saved(shellSettings); + + var orchardHost = scope.Resolve() as DefaultOrchardHost; + if(orchardHost != null) { + var startUpdatedShellsMethod = typeof(DefaultOrchardHost).GetMethod("StartUpdatedShells", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + startUpdatedShellsMethod.Invoke(orchardHost, null); + } + } + } + }); + } + + public void Terminating() { + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedShellTrigger.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedShellTrigger.cs new file mode 100644 index 000000000..d5e888fe0 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedShellTrigger.cs @@ -0,0 +1,24 @@ +using Orchard.Environment.Configuration; +using Orchard.Environment.Descriptor; +using Orchard.Environment.Descriptor.Models; +using Orchard.Environment.Extensions; + +namespace Orchard.MessageBus.Services { + [OrchardFeature("Orchard.MessageBus.DistributedShellRestart")] + public class DistributedShellTrigger : IShellDescriptorManagerEventHandler, IShellSettingsManagerEventHandler { + + private readonly IMessageBus _messageBus; + + public DistributedShellTrigger(IShellSettingsManager shellSettingsManager, IMessageBus messageBus, IShellSettingsManagerEventHandler shellSettingsManagerEventHandler) { + _messageBus = messageBus; + } + + void IShellDescriptorManagerEventHandler.Changed(ShellDescriptor descriptor, string tenant) { + _messageBus.Publish(DistributedShellStarter.Channel, tenant); + } + + void IShellSettingsManagerEventHandler.Saved(ShellSettings settings) { + _messageBus.Publish(DistributedShellStarter.Channel, settings.Name); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedSignals.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedSignals.cs new file mode 100644 index 000000000..c24661d5a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/DistributedSignals.cs @@ -0,0 +1,35 @@ +using Orchard.Caching; +using Orchard.Environment; +using Orchard.Environment.Extensions; +using Orchard.MessageBus.Services; +using Orchard.Services; + +namespace Orchard.MessageBus.Services { + [OrchardFeature("Orchard.MessageBus.DistributedSignals")] + [OrchardSuppressDependency("Orchard.Caching.Signals")] + public class DistributedSignals : Signals, ISignals, IOrchardShellEvents { + private readonly IMessageBus _messageBus; + + public DistributedSignals(IMessageBus messageBus) { + _messageBus = messageBus; + } + + void ISignals.Trigger(T signal) { + base.Trigger(signal); + _messageBus.Publish("Signal", signal.ToString()); + } + + IVolatileToken ISignals.When(T signal) { + return base.When(signal); + } + + public void Activated() { + _messageBus.Subscribe("Signal", (channel, message) => { + base.Trigger(message); + }); + } + + public void Terminating() { + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/HostNameProvider.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/HostNameProvider.cs new file mode 100644 index 000000000..af4aeb48c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/HostNameProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Orchard.MessageBus.Services { + public class HostNameProvider : IHostNameProvider { + + public string GetHostName() { + // use the current host and the process id as two servers could run on the same machine + return System.Net.Dns.GetHostName() + ":" + System.Diagnostics.Process.GetCurrentProcess().Id; + } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IHostNameProvider.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IHostNameProvider.cs new file mode 100644 index 000000000..5e1583c0b --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IHostNameProvider.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Orchard.MessageBus.Services { + public interface IHostNameProvider : IDependency { + string GetHostName(); + } +} diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IMessageBroker.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IMessageBroker.cs new file mode 100644 index 000000000..d7d5a1711 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IMessageBroker.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Orchard.MessageBus.Services { + public interface IMessageBroker : ISingletonDependency { + void Subscribe(string channel, Action handler); + void Publish(string channel, string message); + } +} diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IMessageBus.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IMessageBus.cs new file mode 100644 index 000000000..3e691babf --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/IMessageBus.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Orchard.MessageBus.Models; + +namespace Orchard.MessageBus.Services { + public interface IMessageBus : ISingletonDependency { + void Subscribe(string channel, Action handler); + void Publish(string channel, string message); + } +} diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Services/MessageBusNotificationProvider.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/MessageBusNotificationProvider.cs new file mode 100644 index 000000000..715e69a4d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Services/MessageBusNotificationProvider.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Orchard.Localization; +using Orchard.UI.Admin.Notification; +using Orchard.UI.Notify; +using System.Linq; + +namespace Orchard.MessageBus.Services { + public class MessageBusNotificationProvider : INotificationProvider { + private readonly IEnumerable _messageBrokers; + + public MessageBusNotificationProvider(IEnumerable messageBrokers) { + _messageBrokers = messageBrokers; + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + public IEnumerable GetNotifications() { + + if (!_messageBrokers.Any()) { + yield return new NotifyEntry { Message = T("You need to enable an message bus broker implementation like SQL Server Service Broker."), Type = NotifyType.Warning }; + } + } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/SqlServerBrokerMigrations.cs b/src/Orchard.Web/Modules/Orchard.MessageBus/SqlServerBrokerMigrations.cs new file mode 100644 index 000000000..cc29857c2 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/SqlServerBrokerMigrations.cs @@ -0,0 +1,25 @@ +using System; +using Orchard.ContentManagement.MetaData; +using Orchard.Data.Migration; +using Orchard.Environment.Extensions; +using Orchard.MessageBus.Models; + +namespace Orchard.MessageBus { + [OrchardFeature("Orchard.MessageBus.SqlServerServiceBroker")] + public class SqlServerBrokerMigrations : DataMigrationImpl { + + public int Create() { + + SchemaBuilder.CreateTable("MessageRecord", + table => table + .Column("Id", c => c.PrimaryKey().Identity()) + .Column("Publisher", c => c.WithLength(255)) + .Column("Channel", c => c.WithLength(255)) + .Column("Message", c => c.Unlimited()) + .Column("CreatedUtc") + ); + + return 1; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Styles/Web.config b/src/Orchard.Web/Modules/Orchard.MessageBus/Styles/Web.config new file mode 100644 index 000000000..817198995 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Styles/Web.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.MessageBus/Web.config b/src/Orchard.Web/Modules/Orchard.MessageBus/Web.config new file mode 100644 index 000000000..6fc84bc30 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MessageBus/Web.config @@ -0,0 +1,41 @@ + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.sln b/src/Orchard.sln index 1a9be3e7f..216369f8b 100644 --- a/src/Orchard.sln +++ b/src/Orchard.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30501.0 +VisualStudioVersion = 12.0.30723.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}" EndProject @@ -243,6 +243,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Azure.MediaServices EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.AuditTrail", "Orchard.Web\Modules\Orchard.AuditTrail\Orchard.AuditTrail.csproj", "{3DD574CD-9C5D-4A45-85E1-EBBA64C22B5F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.MessageBus", "Orchard.Web\Modules\Orchard.MessageBus\Orchard.MessageBus.csproj", "{ED715544-E649-4F48-B8EE-9368C41C3AC0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CodeCoverage|Any CPU = CodeCoverage|Any CPU @@ -1008,6 +1010,13 @@ Global {3DD574CD-9C5D-4A45-85E1-EBBA64C22B5F}.FxCop|Any CPU.ActiveCfg = Release|Any CPU {3DD574CD-9C5D-4A45-85E1-EBBA64C22B5F}.Release|Any CPU.ActiveCfg = Release|Any CPU {3DD574CD-9C5D-4A45-85E1-EBBA64C22B5F}.Release|Any CPU.Build.0 = Release|Any CPU + {ED715544-E649-4F48-B8EE-9368C41C3AC0}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU + {ED715544-E649-4F48-B8EE-9368C41C3AC0}.Coverage|Any CPU.ActiveCfg = Release|Any CPU + {ED715544-E649-4F48-B8EE-9368C41C3AC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED715544-E649-4F48-B8EE-9368C41C3AC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED715544-E649-4F48-B8EE-9368C41C3AC0}.FxCop|Any CPU.ActiveCfg = Release|Any CPU + {ED715544-E649-4F48-B8EE-9368C41C3AC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED715544-E649-4F48-B8EE-9368C41C3AC0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1087,5 +1096,6 @@ Global {7528BF74-25C7-4ABE-883A-443B4EEC4776} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {14A96B1A-9DC9-44C8-A675-206329E15263} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {3DD574CD-9C5D-4A45-85E1-EBBA64C22B5F} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} + {ED715544-E649-4F48-B8EE-9368C41C3AC0} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} EndGlobalSection EndGlobal