diff --git a/Squidex.sln b/Squidex.sln index 7c456fe9f..574746110 100644 --- a/Squidex.sln +++ b/Squidex.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +VisualStudioVersion = 15.0.26228.12 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squidex", "src\Squidex\Squidex.csproj", "{61F6BBCE-A080-4400-B194-70E2F5D2096E}" EndProject @@ -36,6 +36,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{1667F3B4 EndProject Project("{13B669BE-BB05-4DDF-9536-439F39A36129}") = "GenerateLanguages", "tools\GenerateLanguages\GenerateLanguages.csproj", "{927E1F1C-95F0-4991-B33F-603977204B02}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squidex.Infrastructure.RabbitMq", "src\Squidex.Infrastructure.RabbitMq\Squidex.Infrastructure.RabbitMq.csproj", "{C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -162,6 +164,18 @@ Global {927E1F1C-95F0-4991-B33F-603977204B02}.Release|x64.Build.0 = Release|Any CPU {927E1F1C-95F0-4991-B33F-603977204B02}.Release|x86.ActiveCfg = Release|Any CPU {927E1F1C-95F0-4991-B33F-603977204B02}.Release|x86.Build.0 = Release|Any CPU + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}.Debug|x64.ActiveCfg = Debug|Any CPU + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}.Debug|x64.Build.0 = Debug|Any CPU + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}.Debug|x86.ActiveCfg = Debug|Any CPU + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}.Debug|x86.Build.0 = Debug|Any CPU + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}.Release|Any CPU.Build.0 = Release|Any CPU + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}.Release|x64.ActiveCfg = Release|Any CPU + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}.Release|x64.Build.0 = Release|Any CPU + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}.Release|x86.ActiveCfg = Release|Any CPU + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -180,5 +194,6 @@ Global {8B074219-F69A-4E41-83C6-12EE1E647779} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {D7166C56-178A-4457-B56A-C615C7450DEE} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {927E1F1C-95F0-4991-B33F-603977204B02} = {1667F3B4-31E6-45B2-90FB-97B1ECFE9874} + {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} EndGlobalSection EndGlobal diff --git a/src/Squidex.Infrastructure.RabbitMq/RabbitMqEventConsumer.cs b/src/Squidex.Infrastructure.RabbitMq/RabbitMqEventConsumer.cs new file mode 100644 index 000000000..c873ada4d --- /dev/null +++ b/src/Squidex.Infrastructure.RabbitMq/RabbitMqEventConsumer.cs @@ -0,0 +1,95 @@ +// ========================================================================== +// RabbitMqEventConsumer.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using RabbitMQ.Client; +using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.Tasks; + +// ReSharper disable InvertIf + +namespace Squidex.Infrastructure.RabbitMq +{ + public sealed class RabbitMqEventConsumer : DisposableObjectBase, IExternalSystem, IEventConsumer + { + private readonly string exchange; + private readonly string streamFilter; + private readonly ConnectionFactory connectionFactory; + private readonly Lazy connection; + private readonly Lazy channel; + + public string Name + { + get { return GetType().Name; } + } + + public string StreamFilter + { + get { return streamFilter; } + } + + public RabbitMqEventConsumer(string uri, string exchange, string streamFilter) + { + Guard.NotNullOrEmpty(uri, nameof(uri)); + Guard.NotNullOrEmpty(exchange, nameof(exchange)); + + connectionFactory = new ConnectionFactory { Uri = uri }; + + connection = new Lazy(connectionFactory.CreateConnection); + channel = new Lazy(() => connection.Value.CreateModel()); + + this.exchange = exchange; + + this.streamFilter = streamFilter; + } + + protected override void DisposeObject(bool disposing) + { + if (connection.IsValueCreated) + { + connection.Value.Close(); + connection.Value.Dispose(); + } + } + + public void Connect() + { + try + { + var currentConnection = connection.Value; + + if (!currentConnection.IsOpen) + { + throw new ConfigurationException($"RabbitMq event bus failed to connect to {connectionFactory.Endpoint}"); + } + } + catch (Exception e) + { + throw new ConfigurationException($"RabbitMq event bus failed to connect to {connectionFactory.Endpoint}", e); + } + } + + public Task ClearAsync() + { + return TaskHelper.Done; + } + + public Task On(Envelope @event) + { + var jsonString = JsonConvert.SerializeObject(@event); + var jsonBytes = Encoding.UTF8.GetBytes(jsonString); + + channel.Value.BasicPublish(exchange, string.Empty, null, jsonBytes); + + return TaskHelper.Done; + } + } +} diff --git a/src/Squidex.Infrastructure.RabbitMq/RabbitMqOptions.cs b/src/Squidex.Infrastructure.RabbitMq/RabbitMqOptions.cs new file mode 100644 index 000000000..39dc6820c --- /dev/null +++ b/src/Squidex.Infrastructure.RabbitMq/RabbitMqOptions.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Squidex.Infrastructure.RabbitMq +{ + class RabbitMqOptions + { + } +} diff --git a/src/Squidex.Infrastructure.RabbitMq/Squidex.Infrastructure.RabbitMq.csproj b/src/Squidex.Infrastructure.RabbitMq/Squidex.Infrastructure.RabbitMq.csproj new file mode 100644 index 000000000..1882daafa --- /dev/null +++ b/src/Squidex.Infrastructure.RabbitMq/Squidex.Infrastructure.RabbitMq.csproj @@ -0,0 +1,15 @@ + + + netstandard1.6 + + + full + True + + + + + + + + \ No newline at end of file diff --git a/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs b/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs index cd364b6b3..1d056c8a9 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs @@ -66,6 +66,8 @@ namespace Squidex.Infrastructure.CQRS.Events public void Next() { + ThrowIfDisposed(); + timer?.Trigger(); } @@ -73,6 +75,8 @@ namespace Squidex.Infrastructure.CQRS.Events { Guard.NotNull(eventConsumer, nameof(eventConsumer)); + ThrowIfDisposed(); + if (timer != null) { return; diff --git a/src/Squidex/Config/Domain/RabbitMqModule.cs b/src/Squidex/Config/Domain/RabbitMqModule.cs new file mode 100644 index 000000000..ce0a9944d --- /dev/null +++ b/src/Squidex/Config/Domain/RabbitMqModule.cs @@ -0,0 +1,47 @@ +// ========================================================================== +// RabbitMqModule.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Autofac; +using Microsoft.Extensions.Configuration; +using Squidex.Infrastructure; +using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.RabbitMq; + +// ReSharper disable InvertIf + +namespace Squidex.Config.Domain +{ + public sealed class RabbitMqModule : Module + { + private IConfiguration Configuration { get; } + + public RabbitMqModule(IConfiguration configuration) + { + Configuration = configuration; + } + + protected override void Load(ContainerBuilder builder) + { + var connectionString = Configuration.GetValue("squidex:eventPublishers:rabbitMq:connectionString"); + var exchange = Configuration.GetValue("squidex:eventPublishers:rabbitMq:exchange"); + var enabled = Configuration.GetValue("squidex:eventPublishers:rabbitMq:enabled"); + + if (!string.IsNullOrWhiteSpace(connectionString) && + !string.IsNullOrWhiteSpace(exchange) && + enabled) + { + var streamFilter = Configuration.GetValue("squidex:eventPublishers:rabbitMq:streamFilter"); + + builder.Register(c => new RabbitMqEventConsumer(connectionString, exchange, streamFilter)) + .As() + .As() + .SingleInstance(); + } + } + } +} diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index e40feec77..3306a9828 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -15,7 +15,7 @@ - + PreserveNewest @@ -25,6 +25,7 @@ + diff --git a/src/Squidex/Startup.cs b/src/Squidex/Startup.cs index 3afa27054..88696ec2c 100644 --- a/src/Squidex/Startup.cs +++ b/src/Squidex/Startup.cs @@ -80,6 +80,7 @@ namespace Squidex builder.RegisterModule(new ClusterModule(Configuration)); builder.RegisterModule(new EventStoreModule(Configuration)); builder.RegisterModule(new InfrastructureModule(Configuration)); + builder.RegisterModule(new RabbitMqModule(Configuration)); builder.RegisterModule(new ReadModule(Configuration)); builder.RegisterModule(new StoreModule(Configuration)); builder.RegisterModule(new WebModule(Configuration)); diff --git a/src/Squidex/appsettings.json b/src/Squidex/appsettings.json index b0e5cc2af..08a5cd51e 100644 --- a/src/Squidex/appsettings.json +++ b/src/Squidex/appsettings.json @@ -19,6 +19,14 @@ "databaseName": "Squidex" } }, + "eventPublishers": { + "rabbitMq": { + "connectionString": "amqp://guest:guest@localhost/", + "exchange": "Squidex", + "enabled": false, + "streamFilter": "*" + } + }, "stores": { "type": "mongoDb", "mongoDb": {